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

138 lines
4.7 KiB
Go

package mcp
import (
"context"
"github.com/zzet/gortex/internal/embedding"
"github.com/zzet/gortex/internal/search/rerank"
)
// buildRerankContext assembles the per-request rerank.Context with
// every session-aware data source the server holds: locality, combo,
// frecency, feedback, and churn. Pure structural signals (BM25 rank,
// fan-in / fan-out, MinHash, signature match, recency, community) do
// not depend on session state and read from the graph directly via
// the Context.Graph pointer set by the pipeline call site.
//
// Returned Context is safe to reuse for the lifetime of the request
// but should not be cached across requests — the combo boost map is
// query-specific and the locality fields are session-specific.
func (s *Server) buildRerankContext(ctx context.Context, query string) *rerank.Context {
repo, project := s.sessionLocality(ctx)
rctx := &rerank.Context{
Graph: s.graph,
RepoPrefix: repo,
ProjectID: project,
}
if cr := s.getCommunities(); cr != nil && cr.NodeToComm != nil {
nodeToComm := cr.NodeToComm
rctx.CommunityOf = func(id string) string { return nodeToComm[id] }
}
if s.combo != nil {
// The combo boost fuses two stores: the exact whole-query
// index (BoostMap) and the per-keyword association index
// (KeywordBoostMap). They are max()-merged so a symbol picks
// up the stronger of "this exact query led here before" and
// "queries sharing these keywords led here before" -- the
// exact-query boost is capped higher, so it dominates a
// keyword-only boost whenever both fire. FeedbackSignal reads
// the merged closure unchanged.
boosts := s.combo.BoostMap(query)
kwBoosts := s.combo.KeywordBoostMap(query)
if len(boosts) > 0 || len(kwBoosts) > 0 {
merged := make(map[string]float64, len(boosts)+len(kwBoosts))
for id, v := range kwBoosts {
merged[id] = v
}
for id, v := range boosts {
if existing, ok := merged[id]; !ok || v > existing {
merged[id] = v
}
}
rctx.ComboBoostOf = func(id string) float64 {
if v, ok := merged[id]; ok {
return v
}
return 1.0
}
}
}
if s.frecency != nil && s.frecency.HasData() {
ft := s.frecency
rctx.FrecencyBoostOf = func(id string) float64 { return ft.BoostFor(id) }
}
if s.feedback != nil && s.feedback.HasData() {
fb := s.feedback
rctx.FeedbackOf = func(id string) float64 { return fb.GetSymbolScoreForQuery(id, query) }
}
if s.symHistory != nil {
churn := s.churnCounts()
if len(churn) > 0 {
rctx.ChurnOf = func(id string) int { return churn[id] }
}
}
// HITS authority / hub scores feed the authority rerank signal.
// Both closures normalise against the graph maxima so the signal
// receives values already in [0, 1].
if h := s.getHITS(); h != nil {
hits := h
maxAuth, maxHub := hits.MaxAuth, hits.MaxHub
rctx.AuthorityOf = func(id string) float64 {
if maxAuth <= 0 {
return 0
}
return hits.AuthorityOf(id) / maxAuth
}
rctx.HubOf = func(id string) float64 {
if maxHub <= 0 {
return 0
}
return hits.HubOf(id) / maxHub
}
}
// Centrality: a per-query Random-Walk-with-Restart (Personalized
// PageRank) over the call/reference graph, seeded from the query's
// strongest candidate matches, feeds ProximitySignal — the graph-
// centrality spine of retrieval. The walk runs against the
// immutable adjacency snapshot built by RunAnalysis; nil until then
// (the signal then sits at 0). The actual walk fires once per
// Rerank inside Context.prepare, after the seeds are chosen.
if snap := s.getAdjacency(); snap != nil {
rctx.Centrality = func(seeds []string) map[string]float64 {
return s.personalizedPageRank(snap, seeds)
}
}
// Co-change feeds the rerank pipeline once the git-history mine
// has run (lazily, on the first find_co_changing_symbols call, or
// from an enriched snapshot). Until then the signal sits at 0.
if s.hasCoChangeData() {
rctx.CoChangeOf = s.coChangeScores
}
// Semantic-cosine channel: the in-process static code-embedding
// model (with the baked GloVe word vectors as offline fallback)
// re-scores the BM25 top-N against the query with no ANN index and
// no index-time vector build. Wired unconditionally — the per-class
// weight table damps it hard on identifier / path queries so it
// earns its keep only on natural-language intent queries, where
// BM25 alone cannot bridge "decode bson body" to BindBody. An empty
// query vector leaves the signal at 0, so a pure-identifier query
// is unaffected even before the class damping.
if emb := embedding.SharedCodeEmbedder(); emb != nil {
rctx.EmbedText = embedding.EmbedTextFunc(emb)
if qv, err := emb.Embed(ctx, query); err == nil {
rctx.QueryVec = qv
}
}
return rctx
}