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

128 lines
3.7 KiB
Go

package rerank
import (
"math"
"github.com/zzet/gortex/internal/graph"
)
// ChurnSignal scores by recent modification activity. Hot files
// (frequently touched) are more likely to be the agent's current
// concern. Reads from Context.churnFor which checks ChurnOf, then
// Node.Meta["churn"], then a 0/1 fallback derived from blame.
type ChurnSignal struct{}
func (ChurnSignal) Name() string { return SignalChurn }
func (ChurnSignal) Contribute(_ string, c *Candidate, ctx *Context) float64 {
count := ctx.churnFor(c.Node)
return normLog(count, ctx.churnMax)
}
// RecencySignal scores by recency of the last commit that touched the
// symbol. Reads Node.Meta["last_authored"].timestamp; symbols never
// authored (no blame enrichment) contribute 0. Exponential decay with
// a 30-day half-life so a year-old change still contributes ~6%.
type RecencySignal struct{}
func (RecencySignal) Name() string { return SignalRecency }
const recencyHalfLifeDays = 30.0
const recencySecondsPerDay = 86400.0
func (RecencySignal) Contribute(_ string, c *Candidate, ctx *Context) float64 {
ts := lastAuthoredUnix(c.Node)
if ts <= 0 {
return 0
}
now := ctx.now()
if now <= 0 {
return 0
}
dt := float64(now-ts) / recencySecondsPerDay
if dt < 0 {
dt = 0
}
k := math.Ln2 / recencyHalfLifeDays
return math.Exp(-k * dt)
}
// APISignatureSignal scores by query-vs-function-signature token
// overlap. Targets queries like "validate token return error" against
// `func validateToken(t string) error`. Asymmetric (overlap, not
// jaccard) so a verbose signature isn't penalised for extra tokens.
type APISignatureSignal struct{}
func (APISignatureSignal) Name() string { return SignalAPISignature }
func (APISignatureSignal) Contribute(query string, c *Candidate, _ *Context) float64 {
if c.Node.Kind != graph.KindFunction && c.Node.Kind != graph.KindMethod {
return 0
}
sig := signatureString(c.Node)
if sig == "" {
return 0
}
q := tokenize(query)
if len(q) == 0 {
return 0
}
target := tokenize(sig)
// Stitch the symbol name into the target — the function name is
// the API surface and should count even if the signature only
// carries parameter types.
target = append(target, tokenize(c.Node.Name)...)
return overlap(q, target)
}
// TypeSignatureSignal scores by query-vs-type-signature overlap on
// type / interface / struct-field-bearing nodes. Targets queries like
// "user record id email" against `type User struct { ID; Email }`.
type TypeSignatureSignal struct{}
func (TypeSignatureSignal) Name() string { return SignalTypeSignature }
func (TypeSignatureSignal) Contribute(query string, c *Candidate, _ *Context) float64 {
switch c.Node.Kind {
case graph.KindType, graph.KindInterface, graph.KindEnumMember, graph.KindField:
// In scope.
default:
return 0
}
q := tokenize(query)
if len(q) == 0 {
return 0
}
target := tokenize(c.Node.Name)
if sig := signatureString(c.Node); sig != "" {
target = append(target, tokenize(sig)...)
}
// Pull in qualified name tokens (path/Type.Field) too — useful
// for nested-field queries.
if c.Node.QualName != "" {
target = append(target, tokenize(c.Node.QualName)...)
}
return overlap(q, target)
}
// signatureString extracts the textual signature stored in Node.Meta
// when the extractor captured one. Different language extractors stamp
// different keys; we check the common ones in priority order so callers
// don't need to know the language.
func signatureString(n *graph.Node) string {
if n == nil || n.Meta == nil {
return ""
}
for _, key := range []string{"signature", "sig", "fn_signature", "type_signature"} {
if v, ok := n.Meta[key]; ok {
switch s := v.(type) {
case string:
if s != "" {
return s
}
}
}
}
return ""
}