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
165 lines
5.0 KiB
Go
165 lines
5.0 KiB
Go
package rerank
|
|
|
|
import (
|
|
"unicode"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// OverloadProminenceSignal breaks ties among same-named candidates. When
|
|
// a query is ambiguous — several distinct symbols answer to one
|
|
// identifier (Go interface methods like BindBody across every binding,
|
|
// or a `decode` on each decoder class) — no ranker can know which
|
|
// overload the caller meant, but the likelier ones can be floated toward
|
|
// the top-5 by intrinsic prominence: an exported, non-test, callable
|
|
// definition is a better default answer than an unexported field or a
|
|
// test helper of the same name.
|
|
//
|
|
// The signal fires ONLY on a genuine same-name collision in the current
|
|
// batch (nameGroupCount > 1); a candidate whose name is unique
|
|
// contributes 0, so a non-ambiguous query is never perturbed. Every
|
|
// input — exported shape, test path, node kind — is an AST-tier fact
|
|
// available at index time with no enrichment, so the ordering is
|
|
// identical whether or not semantic enrichment has run.
|
|
type OverloadProminenceSignal struct{}
|
|
|
|
func (OverloadProminenceSignal) Name() string { return SignalOverload }
|
|
|
|
func (OverloadProminenceSignal) Contribute(query string, c *Candidate, ctx *Context) float64 {
|
|
if c == nil || c.Node == nil || ctx == nil {
|
|
return 0
|
|
}
|
|
nm := lowerName(c.Node.Name)
|
|
if nm == "" {
|
|
return 0
|
|
}
|
|
// Identifier query: fire only on a genuine same-name collision —
|
|
// an exact-name lookup where overload disambiguation is the whole
|
|
// problem. On any other class the batch's incidental same-name
|
|
// pairs are noise, and a blanket prominence boost would fight the
|
|
// semantic channel that is doing the real work.
|
|
if ctx.QueryClass == QueryClassSymbol {
|
|
if ctx.nameGroupCount[nm] <= 1 {
|
|
return 0
|
|
}
|
|
return prominenceScore(c.Node)
|
|
}
|
|
// Degraded-identifier profile: a multi-word query whose tokens
|
|
// concatenate to exactly this candidate's name is a case-split
|
|
// identifier lookup ("should bind body with" → ShouldBindBodyWith),
|
|
// not a concept query, for THIS candidate. Longer same-stem
|
|
// variants (ShouldBindBodyWithJSON) share every query token and can
|
|
// out-BM25 the exact match; the exact-match floor puts the queried
|
|
// identifier back on top, with prominence breaking ties among
|
|
// several exact matches (the overload group).
|
|
if exactDegradedNameMatch(query, nm) {
|
|
return 0.55 + 0.45*prominenceScore(c.Node)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// prominenceScore rates a symbol's intrinsic answer-worthiness in
|
|
// [0, 1] from AST-tier facts only.
|
|
func prominenceScore(n *graph.Node) float64 {
|
|
var s float64
|
|
if isLikelyExported(n) {
|
|
s += 0.5
|
|
}
|
|
if !isTestPath(n.FilePath) {
|
|
s += 0.3
|
|
}
|
|
switch n.Kind {
|
|
case graph.KindFunction, graph.KindMethod, graph.KindType, graph.KindInterface:
|
|
s += 0.2
|
|
}
|
|
if s > 1 {
|
|
s = 1
|
|
}
|
|
return s
|
|
}
|
|
|
|
// exactDegradedNameMatch reports whether a multi-word query's tokens
|
|
// concatenate to exactly the (already-lowercased) candidate name,
|
|
// comparing alphanumeric runes only so snake_case names match their
|
|
// space-split degraded form too. Single-token queries return false —
|
|
// they are the symbol class's territory.
|
|
func exactDegradedNameMatch(query, lowerNodeName string) bool {
|
|
qa := alnumFold(query, true)
|
|
if qa == "" {
|
|
return false
|
|
}
|
|
return qa == alnumFold(lowerNodeName, false)
|
|
}
|
|
|
|
// alnumFold lowercases and strips every non-alphanumeric rune. When
|
|
// requireMultiToken is set it returns "" unless the input had 2+
|
|
// whitespace-separated tokens (the degraded-identifier shape).
|
|
func alnumFold(s string, requireMultiToken bool) string {
|
|
var b []rune
|
|
tokens := 0
|
|
inTok := false
|
|
for _, r := range s {
|
|
if unicode.IsSpace(r) {
|
|
inTok = false
|
|
continue
|
|
}
|
|
if !inTok {
|
|
tokens++
|
|
inTok = true
|
|
}
|
|
switch {
|
|
case r >= 'a' && r <= 'z' || r >= '0' && r <= '9':
|
|
b = append(b, r)
|
|
case r >= 'A' && r <= 'Z':
|
|
b = append(b, unicode.ToLower(r))
|
|
case unicode.IsLetter(r) || unicode.IsDigit(r):
|
|
b = append(b, unicode.ToLower(r))
|
|
}
|
|
}
|
|
if requireMultiToken && tokens < 2 {
|
|
return ""
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// lowerName lowercases a symbol name without pulling in strings for a
|
|
// one-liner on the hot path. ASCII-fast, rune-correct for the rest.
|
|
func lowerName(s string) string {
|
|
hasUpper := false
|
|
for _, r := range s {
|
|
if r >= 'A' && r <= 'Z' || unicode.IsUpper(r) {
|
|
hasUpper = true
|
|
break
|
|
}
|
|
}
|
|
if !hasUpper {
|
|
return s
|
|
}
|
|
out := make([]rune, 0, len(s))
|
|
for _, r := range s {
|
|
out = append(out, unicode.ToLower(r))
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
// isLikelyExported reports whether a symbol is part of its module's
|
|
// public surface, using only the name shape and language — no
|
|
// enrichment. A leading underscore marks a private member in Python / JS
|
|
// conventions; Go (and the other case-visibility languages) key
|
|
// export on an uppercase initial; everything else is treated as public
|
|
// unless underscore-prefixed.
|
|
func isLikelyExported(n *graph.Node) bool {
|
|
if n == nil || n.Name == "" {
|
|
return false
|
|
}
|
|
first := []rune(n.Name)[0]
|
|
if first == '_' {
|
|
return false
|
|
}
|
|
switch n.Language {
|
|
case "go", "java", "kotlin", "scala", "csharp":
|
|
return unicode.IsUpper(first)
|
|
}
|
|
return true
|
|
}
|