use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use crate::schema::{EdgeBasis, Graph, Kind, Node};
mod focus;
mod intent;
mod scoring;
pub use focus::{
CompoundAnchor, CompoundOmittedAnchor, CompoundSubgraph, OmittedContextCandidate, Subgraph,
ground_subgraph,
};
pub use scoring::{GroundIndex, RankerConfig};
use intent::query_intent_matches;
use scoring::{
Bm25fNodeShape, Bm25fScorer, bm25f_df, bm25f_score, relation_phrase, reverse_relation_phrase,
};
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Confidence {
Exact,
Strong,
Ambiguous,
Weak,
Fallback,
}
#[derive(Serialize, Clone, Debug)]
pub struct Hit {
pub id: String,
pub score: i64,
pub lexical_score: i64,
pub confidence: Confidence,
pub why: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relation_matches: Vec<RelationMatch>,
#[serde(skip)]
pub anchor: f64,
#[serde(skip)]
pub relation_anchor: bool,
}
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub struct RelationMatch {
pub relation: String,
pub direction: String,
pub phrase: String,
pub endpoint_id: String,
pub endpoint_title: String,
pub endpoint_hits: usize,
pub score_boost: i64,
}
use crate::calibration::{AMBIGUITY_RATIO, DISTINCTIVE_NAME_IDF};
const STOPWORDS: &[&str] = &[
"a", "an", "and", "are", "as", "at", "be", "but", "by", "can", "did", "do", "does", "for",
"from", "had", "has", "have", "how", "i", "in", "is", "it", "its", "me", "my", "need", "of",
"on", "or", "our", "should", "so", "that", "the", "their", "them", "then", "there", "these",
"this", "those", "to", "up", "us", "was", "we", "were", "what", "when", "where", "which",
"will", "with", "would", "you", "your",
];
fn is_stopword(t: &str) -> bool {
STOPWORDS.binary_search(&t).is_ok()
}
fn within_edit1(a: &[u8], b: &[u8]) -> bool {
let (la, lb) = (a.len(), b.len());
if la > lb {
return within_edit1(b, a);
}
if lb - la > 1 {
return false;
}
let (mut i, mut j, mut edited) = (0usize, 0usize, false);
while i < la && j < lb {
if a[i] == b[j] {
i += 1;
j += 1;
} else if edited {
return false;
} else {
edited = true;
if la == lb {
i += 1; }
j += 1; }
}
true
}
fn stem(t: &str) -> String {
let n = t.len();
if n > 5 && t.ends_with("ing") {
return restore_e(&t[..n - 3]);
}
if n > 4 && t.ends_with("ed") {
return restore_e(&t[..n - 2]);
}
if n > 3 && t.ends_with('s') && !t.ends_with("ss") && !t.ends_with("us") && !t.ends_with("is") {
return t[..n - 1].to_string();
}
t.to_string()
}
fn restore_e(base: &str) -> String {
let b = base.as_bytes();
let cvc = b.len() >= 3
&& is_consonant(b[b.len() - 3])
&& !is_consonant(b[b.len() - 2])
&& is_consonant(b[b.len() - 1])
&& !matches!(b[b.len() - 1], b'w' | b'x' | b'y');
if cvc {
format!("{base}e")
} else {
base.to_string()
}
}
fn is_consonant(c: u8) -> bool {
!matches!(c.to_ascii_lowercase(), b'a' | b'e' | b'i' | b'o' | b'u')
}
fn terms_of(query_lower: &str) -> Vec<String> {
query_lower
.split_whitespace()
.map(|t| t.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
.filter(|t| t.chars().count() > 1 && !is_stopword(t))
.map(|t| stem(&t))
.collect()
}
fn raw_terms_of(query_lower: &str) -> Vec<String> {
query_lower
.split_whitespace()
.map(|t| t.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
.filter(|t| t.chars().count() > 1 && !is_stopword(t))
.collect()
}
fn band(score: i64) -> Confidence {
if score >= crate::calibration::BAND_STRONG_MIN {
Confidence::Strong
} else if score >= crate::calibration::BAND_WEAK_MIN {
Confidence::Weak
} else {
Confidence::Fallback
}
}
fn band_anchored(score: i64, anchor: f64, coverage: f64) -> Confidence {
let base = band(score);
if anchor <= 0.0 {
if coverage >= 1.0 && score >= crate::calibration::BAND_WEAK_MIN {
return Confidence::Weak;
}
return match base {
Confidence::Exact | Confidence::Strong | Confidence::Ambiguous | Confidence::Weak => {
Confidence::Fallback
}
other => other,
};
}
base
}
fn is_distinct_code_symbol_name(title: &str) -> bool {
let significant_chars = title.chars().filter(|c| c.is_alphanumeric()).count();
significant_chars >= 6 && title.chars().any(|c| c.is_ascii_alphabetic())
}
fn intent_boosts(
intents: &[&str],
is_code: bool,
is_generic_symbol_name: bool,
matched_identity: usize,
exact: f64,
name_boost: f64,
) -> (f64, f64) {
let generic_code_only = is_code
&& is_generic_symbol_name
&& intents.len() == 1
&& intents.first() == Some(&"code")
&& matched_identity == 0
&& exact <= 0.0
&& name_boost <= 0.0;
if generic_code_only {
return (6.0, 0.0);
}
let boost = intents.len() as f64 * 20.0;
(boost, boost)
}
fn calibrate(hits: &mut [Hit]) {
if hits.len() >= 2 && hits[0].confidence == Confidence::Strong {
let top = hits[0].score as f64;
let runner = hits[1].score as f64;
if top > 0.0 && runner >= AMBIGUITY_RATIO * top {
hits[0].confidence = Confidence::Ambiguous;
}
}
}
pub fn apply_floor(hits: &mut Vec<Hit>) {
let has_confident = hits.iter().any(|h| {
matches!(
h.confidence,
Confidence::Exact | Confidence::Strong | Confidence::Ambiguous
)
});
if has_confident {
hits.retain(|h| h.confidence != Confidence::Fallback);
}
}
fn apply_canonical_dominance(hits: &mut [Hit], graph: &Graph) {
let score_by_id: HashMap<String, i64> = hits.iter().map(|h| (h.id.clone(), h.score)).collect();
for h in hits.iter_mut() {
let best_child = graph
.edges
.iter()
.filter(|e| e.relation == crate::schema::relation::HAS_KNOWLEDGE && e.from == h.id)
.filter_map(|e| score_by_id.get(&e.to).copied())
.max();
if let Some(bc) = best_child
&& bc + 1 > h.score
{
h.score = bc + 1;
h.confidence = band_anchored(h.score, h.anchor, 1.0);
h.why
.push("canonical: front door over its own pages".into());
}
}
}
pub fn ground(graph: &Graph, query: &str, limit: usize) -> Vec<Hit> {
ground_with(graph, &GroundIndex::build(graph), query, limit)
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub enum Scope {
#[default]
All,
Docs,
Code,
Section,
Subkind(String),
}
impl Scope {
pub fn parse(s: &str) -> Option<Scope> {
Some(match s.trim().to_ascii_lowercase().as_str() {
"all" | "" => Scope::All,
"docs" | "doc" => Scope::Docs,
"code" => Scope::Code,
"section" | "sections" => Scope::Section,
other => Scope::Subkind(other.to_string()),
})
}
pub fn admits(&self, node: &Node) -> bool {
use crate::schema::Kind;
let is_code = matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
);
let is_section = node.kind == Kind::Section;
match self {
Scope::All => !is_section,
Scope::Code => is_code,
Scope::Docs => !is_code && !is_section,
Scope::Section => is_section,
Scope::Subkind(s) => node.subkind.as_deref() == Some(s.as_str()),
}
}
}
fn directional_relation_boosts<'a>(
graph: &'a Graph,
terms: &[String],
query_lower: &str,
) -> HashMap<&'a str, Vec<RelationMatch>> {
if terms.is_empty() {
return HashMap::new();
}
let term_set = terms.iter().map(String::as_str).collect::<HashSet<_>>();
let raw_term_set = raw_terms_of(query_lower)
.into_iter()
.collect::<HashSet<_>>();
let node_by_id = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect::<HashMap<_, _>>();
let mut boosts: HashMap<&str, Vec<RelationMatch>> = HashMap::new();
let mut seen_boosts = HashSet::new();
for edge in &graph.edges {
if edge.basis != EdgeBasis::Resolved {
continue;
}
let Some(from) = node_by_id.get(edge.from.as_str()) else {
continue;
};
let Some(to) = node_by_id.get(edge.to.as_str()) else {
continue;
};
if !relation_is_directionally_searchable(graph, &edge.relation) {
continue;
}
let forward_terms =
terms_of(&relation_phrase(&edge.relation, &graph.relation_profiles).to_lowercase());
if !forward_terms.is_empty()
&& forward_terms
.iter()
.all(|term| term_set.contains(term.as_str()))
{
let endpoint_hits = endpoint_term_hits(to, terms);
if endpoint_hits > 0
&& seen_boosts.insert((
canonical_relation_endpoint(&edge.from),
canonical_relation_endpoint(&edge.to),
edge.relation.clone(),
"forward",
))
{
let score_boost = 48 + endpoint_hits as i64 * 8;
boosts
.entry(edge.from.as_str())
.or_default()
.push(RelationMatch {
relation: edge.relation.clone(),
direction: "forward".to_string(),
phrase: relation_phrase(&edge.relation, &graph.relation_profiles),
endpoint_id: to.id.clone(),
endpoint_title: to.title.clone(),
endpoint_hits,
score_boost,
});
}
}
let reverse_terms = terms_of(
&reverse_relation_phrase(&edge.relation, &graph.relation_profiles).to_lowercase(),
);
let raw_reverse_terms = raw_terms_of(
&reverse_relation_phrase(&edge.relation, &graph.relation_profiles).to_lowercase(),
);
if !reverse_terms.is_empty()
&& raw_reverse_terms
.iter()
.all(|term| raw_term_set.contains(term))
{
let endpoint_hits = endpoint_term_hits(from, terms);
if endpoint_hits >= 2
&& seen_boosts.insert((
canonical_relation_endpoint(&edge.to),
canonical_relation_endpoint(&edge.from),
edge.relation.clone(),
"reverse",
))
{
let score_boost = 48 + endpoint_hits as i64 * 8;
boosts
.entry(edge.to.as_str())
.or_default()
.push(RelationMatch {
relation: edge.relation.clone(),
direction: "reverse".to_string(),
phrase: reverse_relation_phrase(&edge.relation, &graph.relation_profiles),
endpoint_id: from.id.clone(),
endpoint_title: from.title.clone(),
endpoint_hits,
score_boost,
});
}
}
}
boosts
}
fn canonical_relation_endpoint(id: &str) -> String {
id.split_once('#')
.map_or(id, |(parent, _)| parent)
.to_string()
}
fn endpoint_term_hits(node: &Node, terms: &[String]) -> usize {
let mut surface = normalized_endpoint_text(&node.id);
surface.push(' ');
surface.push_str(&normalized_endpoint_text(&node.title));
for alias in &node.aliases {
surface.push(' ');
surface.push_str(&normalized_endpoint_text(alias));
}
terms
.iter()
.filter(|term| {
let term = normalized_endpoint_text(term);
!term.is_empty() && surface.contains(&term)
})
.count()
}
fn normalized_endpoint_text(value: &str) -> String {
value
.chars()
.filter(char::is_ascii_alphanumeric)
.flat_map(char::to_lowercase)
.collect()
}
fn relation_is_directionally_searchable(graph: &Graph, relation: &str) -> bool {
graph
.relation_profiles
.get(relation)
.cloned()
.or_else(|| crate::schema::core_relation_profile(relation))
.is_none_or(|profile| profile.searchable)
}
pub fn ground_with(graph: &Graph, index: &GroundIndex, query: &str, limit: usize) -> Vec<Hit> {
ground_scoped(graph, index, query, limit, Scope::All)
}
pub fn ground_scoped(
graph: &Graph,
index: &GroundIndex,
query: &str,
limit: usize,
scope: Scope,
) -> Vec<Hit> {
let q = query.to_lowercase();
if q.trim().is_empty() {
return Vec::new();
}
let terms = terms_of(&q);
let n = graph.nodes.len();
let candidate_indices: Vec<usize> = (0..n).collect();
let mut hits: Vec<Hit> = {
let bdf = bm25f_df(&terms, &index.bm25_fields);
let params = index.config;
let sat = params.weights[1] / (params.k1 + params.weights[1]); let qmax: f64 = terms
.iter()
.map(|t| {
let dft = *bdf.get(t.as_str()).unwrap_or(&0);
if dft == 0 {
0.0
} else {
((n as f64 - dft as f64 + 0.5) / (dft as f64 + 0.5) + 1.0).ln() * sat
}
})
.sum::<f64>()
.max(1e-3);
let scorer = Bm25fScorer {
terms: &terms,
df: &bdf,
n,
avglen: &index.bm25_avglen,
params,
};
let known_coverage = terms
.iter()
.filter(|t| *bdf.get(t.as_str()).unwrap_or(&0) > 0)
.count() as f64
/ terms.len().max(1) as f64;
let directional_relation_boosts = directional_relation_boosts(graph, &terms, &q);
graph
.nodes
.iter()
.enumerate()
.filter(|(i, _)| candidate_indices.binary_search(i).is_ok())
.filter(|(_, node)| scope.admits(node))
.filter_map(|(i, node)| {
let is_code = matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
);
let is_module = node.kind == Kind::Module;
let (s, id_s, matched, matched_identity, matched_name, max_exact_name_idf) =
bm25f_score(
&index.bm25_fields[i],
&scorer,
Bm25fNodeShape { is_code, is_module },
);
let idl = node.id.to_lowercase();
let titlel = node.title.to_lowercase();
let exact_query_example = node
.query_examples
.iter()
.any(|example| example.to_lowercase() == q);
let exact = if idl == q
|| titlel == q
|| node.aliases.iter().any(|a| a.to_lowercase() == q)
{
80.0 } else if titlel.contains(&q)
|| node.aliases.iter().any(|a| a.to_lowercase().contains(&q))
{
6.0 } else {
0.0
};
let query_example_boost = if exact_query_example && !is_code {
60.0
} else {
0.0
};
if s <= 0.0 && exact <= 0.0 && query_example_boost <= 0.0 {
return None;
}
let coverage = matched as f64 / terms.len().max(1) as f64;
let id_coverage = matched_identity as f64 / terms.len().max(1) as f64;
let aliases: Vec<String> = node.aliases.iter().map(|a| a.to_lowercase()).collect();
let intents = query_intent_matches(node, &aliases, &terms);
let term_eq_title =
!titlel.is_empty() && terms.iter().any(|t| t.as_str() == titlel);
let kind_kw = match node.kind {
Kind::Type => terms
.iter()
.any(|t| matches!(t.as_str(), "struct" | "enum" | "type")),
Kind::Trait => terms.iter().any(|t| t == "trait"),
Kind::Module => terms.iter().any(|t| matches!(t.as_str(), "module" | "mod")),
Kind::Function => terms
.iter()
.any(|t| matches!(t.as_str(), "function" | "fn" | "method" | "func")),
_ => false,
};
let name_boost = if term_eq_title && (!is_code || kind_kw) {
20.0
} else {
0.0
};
let (intent_boost, anchor_intent_boost) = intent_boosts(
&intents,
is_code,
!is_distinct_code_symbol_name(&titlel),
matched_identity,
exact,
name_boost,
);
let has_relation_intent = terms.iter().any(|term| is_relation_query_term(term));
let relation_term_hits = if has_relation_intent {
terms
.iter()
.filter(|term| index.bm25_fields[i][5].iter().any(|token| token == *term))
.count()
} else {
0
};
let relation_boost = (relation_term_hits as f64) * 12.0;
let relation_matches = directional_relation_boosts
.get(node.id.as_str())
.cloned()
.unwrap_or_default();
let directional_relation_boost =
relation_matches.iter().map(|m| m.score_boost).sum::<i64>() as f64;
let relation_anchor = directional_relation_boost > 0.0;
let code_action = code_action_subject_match(node, &terms);
let code_action_boost = code_action
.as_ref()
.map_or(0.0, |signal| signal.score_boost);
let score = ((s / qmax) * 100.0
+ exact
+ query_example_boost
+ intent_boost
+ relation_boost
+ directional_relation_boost
+ code_action_boost
+ matched_identity as f64
+ name_boost)
.round() as i64;
let distinctive_name =
max_exact_name_idf >= DISTINCTIVE_NAME_IDF && known_coverage >= 0.6;
let anchored = exact > 0.0
|| query_example_boost > 0.0
|| anchor_intent_boost > 0.0
|| name_boost > 0.0
|| code_action_boost > 0.0
|| relation_anchor
|| distinctive_name
|| (id_coverage >= 0.4 && matched_name > 0 && known_coverage >= 0.6);
let anchor = if anchored {
id_s + exact
+ query_example_boost
+ anchor_intent_boost
+ name_boost
+ code_action_boost
+ directional_relation_boost
} else {
0.0
};
let rank_score =
route_score(score as f64, anchor, matched_identity, coverage).round() as i64;
let mut why = vec![format!(
"bm25f {s:.2} (identity {id_s:.2}, cover {:.0}%)",
coverage * 100.0
)];
if !intents.is_empty() {
why.push(format!("query intent match: {}", intents.join(", ")));
}
if relation_term_hits > 0 {
why.push(format!(
"relation context match: {relation_term_hits} terms"
));
}
if relation_anchor {
let relations = relation_matches
.iter()
.map(|m| format!("{} {} {}", m.direction, m.relation, m.endpoint_id))
.collect::<Vec<_>>()
.join(", ");
why.push(format!(
"directed relation match: +{} ({relations})",
directional_relation_boost as i64,
));
}
if let Some(code_action) = code_action {
why.push(format!(
"code action subject match: {} identifier terms",
code_action.identifier_hits
));
}
if query_example_boost > 0.0 {
why.push("exact query_example match".to_string());
}
Some(Hit {
id: node.id.clone(),
score: rank_score,
lexical_score: score,
confidence: band_anchored(score, anchor, coverage),
why,
relation_matches,
anchor,
relation_anchor,
})
})
.collect()
};
apply_canonical_dominance(&mut hits, graph);
let title_hit: HashSet<&str> = graph
.nodes
.iter()
.enumerate()
.filter(|(i, _)| {
terms
.iter()
.any(|term| index.lc_titles[*i].contains(term.as_str()))
})
.map(|(_, node)| node.id.as_str())
.collect();
let title_term_hits: HashMap<&str, usize> = graph
.nodes
.iter()
.enumerate()
.map(|(i, node)| {
(
node.id.as_str(),
terms
.iter()
.filter(|term| index.lc_titles[i].contains(term.as_str()))
.count(),
)
})
.collect();
hits.sort_by(|a, b| {
b.score
.cmp(&a.score)
.then_with(|| {
title_hit
.contains(b.id.as_str())
.cmp(&title_hit.contains(a.id.as_str()))
})
.then_with(|| {
title_term_hits
.get(b.id.as_str())
.cmp(&title_term_hits.get(a.id.as_str()))
})
.then_with(|| a.id.cmp(&b.id))
});
calibrate(&mut hits);
hits.truncate(limit);
hits
}
fn route_score(score: f64, anchor: f64, matched_identity: usize, coverage: f64) -> f64 {
if anchor <= 0.0 && matched_identity == 0 && coverage < 1.0 {
score * coverage.max(0.25)
} else {
score
}
}
#[derive(Debug, Clone, Copy)]
struct CodeActionSignal {
identifier_hits: usize,
score_boost: f64,
}
fn code_action_subject_match(node: &Node, terms: &[String]) -> Option<CodeActionSignal> {
if !matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
) || !query_has_code_action_intent(terms)
{
return None;
}
let subject_terms = terms
.iter()
.filter(|term| !is_code_action_control_term(term))
.map(|term| normalized_identifier_text(term))
.filter(|term| !term.is_empty())
.collect::<HashSet<_>>();
if subject_terms.is_empty() {
return None;
}
let segments = code_identifier_segments(node);
let identifier_hits = subject_terms
.iter()
.filter(|term| segments.contains(term.as_str()))
.count();
if identifier_hits == 0 {
return None;
}
let wants_function = terms.iter().any(|term| {
matches!(
term.as_str(),
"definition"
| "function"
| "fn"
| "implement"
| "implementation"
| "implemented"
| "method"
| "source"
)
});
let kind_boost = match node.kind {
Kind::Function => 16.0,
Kind::Type | Kind::Trait => 12.0,
Kind::Module => 8.0,
_ => 0.0,
};
let function_boost = if wants_function && node.kind == Kind::Function {
18.0
} else {
0.0
};
Some(CodeActionSignal {
identifier_hits,
score_boost: identifier_hits as f64 * 28.0 + kind_boost + function_boost,
})
}
fn query_has_code_action_intent(terms: &[String]) -> bool {
terms.iter().any(|term| is_code_action_control_term(term))
}
fn is_code_action_control_term(term: &str) -> bool {
matches!(
term,
"call"
| "caller"
| "callee"
| "change"
| "changing"
| "definition"
| "function"
| "fn"
| "implement"
| "implementation"
| "implemented"
| "method"
| "modify"
| "source"
| "trace"
| "type"
| "usage"
| "use"
| "used"
| "uses"
)
}
fn code_identifier_segments(node: &Node) -> HashSet<String> {
let mut out = HashSet::new();
push_identifier_segments(&mut out, &node.id);
push_identifier_segments(&mut out, &node.title);
out
}
fn push_identifier_segments(out: &mut HashSet<String>, value: &str) {
for segment in value.split([':', '.', '#', '/', '-']) {
let normalized = normalized_identifier_text(segment);
if !normalized.is_empty() {
out.insert(normalized);
}
for part in segment.split('_') {
let normalized_part = normalized_identifier_text(part);
if normalized_part.len() >= 6 {
out.insert(normalized_part);
}
}
}
let normalized = normalized_identifier_text(value);
if !normalized.is_empty() {
out.insert(normalized);
}
}
fn normalized_identifier_text(value: &str) -> String {
value
.chars()
.filter(char::is_ascii_alphanumeric)
.flat_map(char::to_lowercase)
.collect()
}
fn is_relation_query_term(term: &str) -> bool {
matches!(
term,
"call"
| "consume"
| "contract"
| "depend"
| "dispatch"
| "implement"
| "link"
| "produce"
| "reference"
| "relate"
| "require"
)
}
#[cfg(test)]
mod band_tests {
use super::*;
use crate::schema::{Edge, EdgeBasis, RelationProfile};
fn test_hit(id: &str, score: i64, confidence: Confidence) -> Hit {
Hit {
id: id.into(),
score,
lexical_score: score,
confidence,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
}
}
#[test]
fn apply_floor_drops_trailing_fallback_only_when_a_confident_hit_exists() {
let mut hits = vec![
test_hit("a", 60, Confidence::Strong),
test_hit("b", 20, Confidence::Fallback),
test_hit("c", 10, Confidence::Fallback),
];
apply_floor(&mut hits);
assert_eq!(
hits.iter().map(|h| h.id.as_str()).collect::<Vec<_>>(),
["a"]
);
let mut weak = vec![
test_hit("a", 30, Confidence::Weak),
test_hit("b", 20, Confidence::Fallback),
];
apply_floor(&mut weak);
assert_eq!(weak.len(), 2, "no confident hit ⇒ list untouched");
}
#[test]
fn calibrate_demotes_strong_top_when_runner_up_is_within_ambiguity_ratio() {
let mut close = vec![
test_hit("a", 100, Confidence::Strong),
test_hit("b", 70, Confidence::Strong),
];
calibrate(&mut close);
assert_eq!(close[0].confidence, Confidence::Ambiguous);
let mut clear = vec![
test_hit("a", 100, Confidence::Strong),
test_hit("b", 69, Confidence::Strong),
];
calibrate(&mut clear);
assert_eq!(clear[0].confidence, Confidence::Strong);
}
#[test]
fn canonical_dominance_lifts_a_parent_above_its_best_matching_child() {
let graph = Graph {
nodes: Vec::new(),
edges: vec![Edge {
from: "skill.x".into(),
to: "doc.x#page".into(),
relation: crate::schema::relation::HAS_KNOWLEDGE.into(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
..Default::default()
};
let mut hits = vec![
test_hit("doc.x#page", 80, Confidence::Strong),
test_hit("skill.x", 50, Confidence::Weak),
];
apply_canonical_dominance(&mut hits, &graph);
let parent = hits.iter().find(|h| h.id == "skill.x").unwrap();
assert_eq!(parent.score, 81, "parent lifted to best_child + 1");
assert!(parent.why.iter().any(|w| w.contains("canonical")));
}
#[test]
fn adding_a_garbage_term_never_raises_a_hits_band() {
let band_rank = |c: Confidence| match c {
Confidence::Exact => 4,
Confidence::Strong => 3,
Confidence::Ambiguous => 2,
Confidence::Weak => 1,
Confidence::Fallback => 0,
};
let node = |id: &str, title: &str, summary: &str, aliases: &[&str]| Node {
id: id.into(),
kind: Kind::Doc,
subkind: None,
title: title.into(),
summary: summary.into(),
aliases: aliases
.iter()
.map(std::string::ToString::to_string)
.collect(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["f.md".into()],
span: None,
partition: None,
};
let graph = Graph {
nodes: vec![
node(
"doc.retry",
"Retry Policy",
"backoff and dead letter routing",
&["retry"],
),
node(
"doc.sched",
"Scheduler",
"the task scheduler loop",
&["scheduler"],
),
node("doc.obs", "Observability", "logs metrics traces", &[]),
],
..Default::default()
};
for base in ["retry policy", "scheduler loop", "logs metrics", "retry"] {
let with_garbage = format!("{base} zzqwxborg");
let base_hits = ground(&graph, base, 5);
let noisy_hits = ground(&graph, &with_garbage, 5);
for bh in &base_hits {
if let Some(nh) = noisy_hits.iter().find(|h| h.id == bh.id) {
assert!(
band_rank(nh.confidence) <= band_rank(bh.confidence),
"adding a garbage term raised {}'s band from {:?} to {:?} (query {base:?})",
bh.id,
bh.confidence,
nh.confidence
);
}
}
}
}
#[test]
fn ranker_config_is_injected_not_read_from_env() {
let graph = Graph {
nodes: vec![Node {
id: "doc.note".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Note".to_string(),
summary: "kombucha kombucha kombucha fermentation notes".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["note.md".to_string()],
span: None,
partition: None,
}],
edges: Vec::new(),
..Default::default()
};
let default_index = GroundIndex::build(&graph);
let boosted = RankerConfig {
weights: [5.0, 8.0, 40.0, 6.0, 4.0, 3.0], ..RankerConfig::default()
};
let boosted_index = GroundIndex::build_with_config(&graph, boosted);
let default_score =
ground_with(&graph, &default_index, "kombucha fermentation", 5)[0].lexical_score;
let boosted_score =
ground_with(&graph, &boosted_index, "kombucha fermentation", 5)[0].lexical_score;
assert_ne!(
default_score, boosted_score,
"an injected RankerConfig must change scoring"
);
let repeat =
ground_with(&graph, &boosted_index, "kombucha fermentation", 5)[0].lexical_score;
assert_eq!(boosted_score, repeat);
}
#[test]
fn bands_are_calibrated_to_score_distribution() {
assert_eq!(
band(45),
Confidence::Strong,
"45 is strong (clean accuracy 100% there)"
);
assert_eq!(band(44), Confidence::Weak);
assert_eq!(band(25), Confidence::Weak);
assert_eq!(band(24), Confidence::Fallback);
assert_eq!(
band(32),
Confidence::Weak,
"garbage-range scores stay below strong"
);
}
#[test]
fn anchor_caps_body_only_mentions_below_strong() {
assert_eq!(band_anchored(60, 0.0, 1.0), Confidence::Weak);
assert_eq!(band_anchored(48, 0.0, 1.0), Confidence::Weak);
assert_eq!(band_anchored(30, 0.0, 1.0), Confidence::Weak);
assert_eq!(band_anchored(25, 0.0, 1.0), Confidence::Weak);
assert_eq!(band_anchored(10, 0.0, 1.0), Confidence::Fallback);
assert_eq!(band_anchored(30, 0.0, 0.5), Confidence::Fallback);
assert_eq!(band_anchored(60, 0.5, 1.0), Confidence::Strong);
assert_eq!(band_anchored(30, 0.9, 1.0), Confidence::Weak);
}
#[test]
fn code_symbol_names_require_distinctive_bare_terms() {
assert!(is_distinct_code_symbol_name("load_settings"));
assert!(is_distinct_code_symbol_name("config2"));
assert!(!is_distinct_code_symbol_name("run"));
assert!(!is_distinct_code_symbol_name("id"));
assert!(!is_distinct_code_symbol_name("123456"));
}
#[test]
fn route_score_penalizes_partial_body_only_mentions() {
assert_eq!(route_score(60.0, 0.0, 0, 0.5), 30.0);
assert_eq!(route_score(60.0, 0.0, 0, 1.0), 60.0);
assert_eq!(route_score(60.0, 0.2, 0, 0.5), 60.0);
assert_eq!(route_score(60.0, 0.0, 1, 0.5), 60.0);
}
#[test]
fn anchored_typo_match_beats_partial_body_only_match() {
let graph = Graph {
nodes: vec![
Node {
id: "doc.material-inventory".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Material Inventory".to_string(),
summary: "Home furniture materials, finishes, and care notes.".to_string(),
aliases: Vec::new(),
tags: vec!["veneer".to_string(), "desk".to_string()],
query_examples: vec![
"Desk: walnut veneer over plywood. Do not sand aggressively.".to_string(),
],
source_files: vec!["material-inventory.md".to_string()],
span: None,
partition: None,
},
Node {
id: "skill.wood-care".to_string(),
kind: Kind::Skill,
subkind: None,
title: "wood-care".to_string(),
summary: "Care and repair for wood, veneer, water rings, and scratches."
.to_string(),
aliases: vec![
"veneer".to_string(),
"scratch".to_string(),
"wood repair".to_string(),
],
tags: Vec::new(),
query_examples: vec!["fixing small scratches in walnut furniture".to_string()],
source_files: vec!["skills/wood-care/SKILL.md".to_string()],
span: None,
partition: None,
},
],
edges: Vec::new(),
..Default::default()
};
let hits = ground(&graph, "veneer desk skratch", 5);
assert_eq!(
hits.first().map(|hit| hit.id.as_str()),
Some("skill.wood-care")
);
assert!(
hits.iter()
.any(|hit| hit.id == "doc.material-inventory"
&& hit.confidence == Confidence::Fallback),
"body-only material doc should remain a low-confidence support hit"
);
}
#[test]
fn generic_code_intent_does_not_make_body_only_helper_strong() {
let graph = Graph {
nodes: vec![Node {
id: "fn.tests::run".to_string(),
kind: Kind::Function,
subkind: None,
title: "run".to_string(),
summary: String::new(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: vec![
"debug package eval gates code architecture docs inspect".to_string(),
],
source_files: vec!["tests/cli_graph.rs".to_string()],
span: None,
partition: None,
}],
edges: Vec::new(),
..Default::default()
};
let hits = ground(
&graph,
"debug package eval gates code architecture docs inspect",
5,
);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].id, "fn.tests::run");
assert_eq!(hits[0].confidence, Confidence::Weak);
}
#[test]
fn mostly_unknown_query_does_not_anchor_partial_name_match() {
let graph = Graph {
nodes: vec![
Node {
id: "skill.task-scheduling".to_string(),
kind: Kind::Skill,
subkind: None,
title: "task-scheduling".to_string(),
summary: "Scheduling tasks with retry, backoff, and dead-letter queues."
.to_string(),
aliases: vec!["task scheduler".to_string()],
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["skills/task-scheduling/SKILL.md".to_string()],
span: None,
partition: None,
},
Node {
id: "doc.runbook".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Operations Runbook".to_string(),
summary: "Symptoms and fixes for task and worker failures.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["runbook.md".to_string()],
span: None,
partition: None,
},
],
edges: Vec::new(),
..Default::default()
};
let hits = ground(&graph, "task scheduler yoga meditation mindfulness", 5);
let top = hits.first().expect("the familiar terms still match");
assert!(
matches!(top.confidence, Confidence::Weak | Confidence::Fallback),
"a mostly-unknown query must not answer confidently, got {:?} for {}",
top.confidence,
top.id
);
}
#[test]
fn code_action_query_prefers_named_function_over_nearby_type_context() {
let graph = Graph {
nodes: vec![
code_node(
"type.policy::Backoff",
Kind::Type,
"Backoff",
"Backoff configuration used by RetryPolicy delay_ms implementation.",
),
code_node(
"type.policy::RetryPolicy",
Kind::Type,
"RetryPolicy",
"Retry policy configuration with max attempts and backoff.",
),
code_node(
"fn.policy::RetryPolicy::delay_ms",
Kind::Function,
"delay_ms",
"Compute the delay for a retry attempt.",
),
],
edges: Vec::new(),
..Default::default()
};
let hits = ground(&graph, "RetryPolicy delay_ms implementation", 5);
assert_eq!(
hits.first().map(|hit| hit.id.as_str()),
Some("fn.policy::RetryPolicy::delay_ms")
);
assert!(
hits[0]
.why
.iter()
.any(|why| why.contains("code action subject match")),
"top hit should explain the code-action identifier signal"
);
}
#[test]
fn resolved_relation_surfaces_are_searchable_without_body_mentions() {
let graph = Graph {
nodes: vec![
Node {
id: "doc.router".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Router".to_string(),
summary: "Request routing policy.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["router.md".to_string()],
span: None,
partition: None,
},
Node {
id: "doc.auth-contract".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Auth Contract".to_string(),
summary: "Authentication requirements.".to_string(),
aliases: vec!["login rules".to_string()],
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["auth.md".to_string()],
span: None,
partition: None,
},
],
edges: vec![Edge {
from: "doc.router".to_string(),
to: "doc.auth-contract".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
..Default::default()
};
let hits = ground(&graph, "depends on auth contract", 5);
assert_eq!(hits.first().map(|hit| hit.id.as_str()), Some("doc.router"));
assert!(
hits[0].why.iter().any(|why| why.contains("bm25f")),
"relation search should flow through normal BM25F evidence"
);
assert_eq!(hits[0].relation_matches.len(), 1);
let relation_match = &hits[0].relation_matches[0];
assert_eq!(relation_match.relation, "depends_on");
assert_eq!(relation_match.direction, "forward");
assert_eq!(relation_match.phrase, "depends on");
assert_eq!(relation_match.endpoint_id, "doc.auth-contract");
assert_eq!(relation_match.endpoint_title, "Auth Contract");
assert_eq!(relation_match.endpoint_hits, 2);
assert!(relation_match.score_boost > 0);
}
#[test]
fn forward_relation_query_does_not_trigger_reverse_phrase_by_stem_only() {
let graph = Graph {
nodes: vec![
Node {
id: "doc.refund-runbook".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Refund Escalation Runbook".to_string(),
summary: "High value refund process.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["refund.md".to_string()],
span: None,
partition: None,
},
Node {
id: "doc.release-gate".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Refund Release Gate".to_string(),
summary: "Release gate for refund changes.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["gate.md".to_string()],
span: None,
partition: None,
},
],
edges: vec![Edge {
from: "doc.refund-runbook".to_string(),
to: "doc.release-gate".to_string(),
relation: "blocks".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
relation_profiles: [(
"blocks".to_string(),
RelationProfile::new("blocks", "blocked by", 34),
)]
.into_iter()
.collect(),
};
let hits = ground(
&graph,
"make high value refund change that blocks refund release gate",
5,
);
assert_eq!(
hits.first().map(|hit| hit.id.as_str()),
Some("doc.refund-runbook")
);
assert_eq!(hits[0].relation_matches.len(), 1);
assert_eq!(hits[0].relation_matches[0].direction, "forward");
assert_eq!(hits[0].relation_matches[0].endpoint_id, "doc.release-gate");
assert!(
hits.iter()
.find(|hit| hit.id == "doc.release-gate")
.is_none_or(|hit| hit.relation_matches.is_empty()),
"`blocks` should not satisfy the reverse phrase `blocked by` via stemming alone"
);
}
#[test]
fn relation_profiles_control_search_phrases() {
let graph = Graph {
nodes: vec![
Node {
id: "doc.runbook".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Runbook".to_string(),
summary: "Operational playbook.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["runbook.md".to_string()],
span: None,
partition: None,
},
Node {
id: "doc.report".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Daily Report".to_string(),
summary: "Manager report.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["report.md".to_string()],
span: None,
partition: None,
},
],
edges: vec![Edge {
from: "doc.runbook".to_string(),
to: "doc.report".to_string(),
relation: "emits".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
relation_profiles: [(
"emits".to_string(),
RelationProfile::new("zorbles", "zorbeled by", 30),
)]
.into_iter()
.collect(),
};
let hits = ground(&graph, "zorbles", 5);
assert_eq!(hits.first().map(|hit| hit.id.as_str()), Some("doc.runbook"));
}
#[test]
fn a_distinctive_name_anchors_confidence_but_a_common_term_does_not() {
let doc = |id: &str, title: &str, summary: &str| Node {
id: id.to_string(),
kind: Kind::Doc,
subkind: None,
title: title.to_string(),
summary: summary.to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec![format!("{id}.md")],
span: None,
partition: None,
};
let mut nodes = vec![doc(
"doc.carbonara",
"Carbonara",
"A roman egg pasta with pecorino and guanciale.",
)];
for i in 0..8 {
nodes.push(doc(
&format!("doc.other{i}"),
&format!("Recipe {i}"),
"A dish that uses egg among its ingredients.",
));
}
let graph = Graph {
nodes,
edges: Vec::new(),
relation_profiles: Default::default(),
};
let hits = ground(&graph, "carbonara egg", 5);
let carbonara = hits.iter().find(|h| h.id == "doc.carbonara").unwrap();
assert!(
matches!(
carbonara.confidence,
Confidence::Exact | Confidence::Strong | Confidence::Ambiguous
),
"a distinctive name must anchor confidence above Weak, got {:?}",
carbonara.confidence
);
let common = ground(&graph, "egg", 5);
assert!(
common
.first()
.is_none_or(|h| matches!(h.confidence, Confidence::Weak | Confidence::Fallback)),
"a common term alone must not earn a confident band: {:?}",
common.first().map(|h| h.confidence)
);
}
#[test]
fn relation_neighbour_does_not_outrank_an_exact_query_example() {
let doc = |id: &str, title: &str, summary: &str, qe: &[&str]| Node {
id: id.to_string(),
kind: Kind::Doc,
subkind: None,
title: title.to_string(),
summary: summary.to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: qe.iter().map(|s| s.to_string()).collect(),
source_files: vec![format!("{id}.md")],
span: None,
partition: None,
};
let graph = Graph {
nodes: vec![
doc(
"doc.embeddings",
"Choosing an Embedding Model",
"Pick by recall and latency.",
&["which embedding model should I use"],
),
doc("doc.evals", "Writing Evals", "Build a golden set.", &[]),
],
edges: vec![Edge {
from: "doc.evals".to_string(),
to: "doc.embeddings".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
relation_profiles: [(
"depends_on".to_string(),
RelationProfile::new("depends on", "required by", 30),
)]
.into_iter()
.collect(),
};
let hits = ground(&graph, "which embedding model should I use", 5);
assert_eq!(
hits.first().map(|hit| hit.id.as_str()),
Some("doc.embeddings"),
"the doc with the verbatim query_example must outrank its relation neighbour"
);
assert!(!is_relation_query_term("use"));
assert!(is_relation_query_term("depend"));
assert!(is_relation_query_term("implement"));
}
#[test]
fn exact_query_example_lifts_confidence_to_strong() {
let graph = Graph {
nodes: vec![Node {
id: "doc.dlq".to_string(),
kind: Kind::Doc,
subkind: None,
title: "Dead Letter Handling".to_string(),
summary: String::new(),
aliases: vec!["dead letter".to_string()],
tags: Vec::new(),
query_examples: vec!["what happens to messages that keep failing".to_string()],
source_files: vec!["dlq.md".to_string()],
span: None,
partition: None,
}],
edges: Vec::new(),
..Default::default()
};
let hits = ground(&graph, "what happens to messages that keep failing", 1);
assert_eq!(hits.first().map(|hit| hit.id.as_str()), Some("doc.dlq"));
assert_eq!(
hits.first().map(|hit| hit.confidence),
Some(Confidence::Strong)
);
assert!(
hits.first()
.is_some_and(|hit| hit.why.iter().any(|why| why == "exact query_example match"))
);
}
fn code_node(id: &str, kind: Kind, title: &str, summary: &str) -> Node {
Node {
id: id.to_string(),
kind,
subkind: None,
title: title.to_string(),
summary: summary.to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec![format!("{}.rs", title.to_ascii_lowercase())],
span: None,
partition: None,
}
}
}