use std::collections::{HashMap, HashSet};
use serde::Serialize;
use crate::schema::{Edge, EdgeBasis, Graph, Kind, Node, RelationProfile, core_relation_profile};
use super::{
Confidence, Hit, ground, is_distinct_code_symbol_name,
scoring::{relation_phrase, reverse_relation_phrase},
terms_of,
};
#[derive(Serialize, Clone, Debug, Default)]
pub struct Subgraph {
pub hits: Vec<Hit>,
pub expanded: Vec<String>,
pub edges: Vec<Edge>,
pub route: String,
pub context_order: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compound: Option<CompoundSubgraph>,
#[serde(default, skip_serializing_if = "is_zero_usize")]
pub omitted_context: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub omitted_context_candidates: Vec<OmittedContextCandidate>,
}
#[derive(Serialize, Clone, Debug, PartialEq)]
pub struct CompoundSubgraph {
pub primary: String,
pub anchors: Vec<CompoundAnchor>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub omitted_anchors: Vec<CompoundOmittedAnchor>,
pub coverage: f64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub missing_terms: Vec<String>,
}
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub struct CompoundAnchor {
pub node_id: String,
pub matched_terms: Vec<String>,
pub confidence: Confidence,
pub score: i64,
}
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub struct CompoundOmittedAnchor {
pub node_id: String,
pub matched_terms: Vec<String>,
pub confidence: Confidence,
pub score: i64,
pub reason: String,
}
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
pub struct OmittedContextCandidate {
pub node_id: String,
pub score: i64,
pub reason: String,
}
fn is_zero_usize(value: &usize) -> bool {
*value == 0
}
const TASK_SEED_LEXICAL_RATIO: f64 = 0.5;
const SYMBOL_SEED_LEXICAL_RATIO: f64 = 0.3;
const DEFAULT_CONTEXT_BUDGET: usize = 48;
const MAX_SEED_CONTEXT: usize = 16;
const MAX_ITEMS_PER_SOURCE: usize = 8;
const MAX_OMITTED_CONTEXT_CANDIDATES: usize = 12;
pub fn ground_subgraph(
graph: &Graph,
query: &str,
limit: usize,
depth: usize,
width: usize,
) -> Subgraph {
let hits = ground(graph, query, limit);
let top_is_confident = hits.first().is_some_and(|hit| {
matches!(
hit.confidence,
Confidence::Exact | Confidence::Strong | Confidence::Ambiguous
)
});
let terms = terms_of(&query.to_lowercase());
let route = select_subgraph_route(&hits, graph, &terms);
let seed_ratio = if terms.len() <= 1 {
SYMBOL_SEED_LEXICAL_RATIO
} else {
TASK_SEED_LEXICAL_RATIO
};
let walk_references = should_walk_references(graph, &terms, hits.first());
let top_lexical = hits.iter().map(|h| h.lexical_score).max().unwrap_or(0) as f64;
let floor = (seed_ratio * top_lexical).ceil() as i64;
let route_is_anchored =
node_by_id(graph, &route).is_some_and(|node| route_term_count(node, &terms) > 0);
let mut seeds: Vec<&Hit> = hits
.iter()
.filter(|h| {
(h.id == route || h.lexical_score >= floor)
&& (!top_is_confident
|| h.confidence != Confidence::Fallback
|| is_high_value_support_seed(graph, h, &terms))
&& should_seed_hit(graph, h, &route, route_is_anchored, &terms)
})
.collect();
let mut seed_id_set = seeds
.iter()
.map(|hit| hit.id.as_str())
.collect::<HashSet<_>>();
for hit in &hits {
if seed_id_set.contains(hit.id.as_str()) || hit.lexical_score < floor {
continue;
}
if !is_connected_code_support_seed(graph, hit, &terms, &seed_id_set, walk_references) {
continue;
}
seed_id_set.insert(hit.id.as_str());
seeds.push(hit);
}
let seed_ids: Vec<String> = seeds.iter().map(|h| h.id.clone()).collect();
let mut visited: HashSet<String> = seed_ids.iter().cloned().collect();
let mut frontier: Vec<String> = seed_ids.clone();
let mut edges: Vec<Edge> = Vec::new();
let mut edge_seen: HashSet<(String, String, String)> = HashSet::new();
let node_by_id_map: HashMap<&str, &Node> = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect();
for _ in 0..depth {
let mut next: Vec<String> = Vec::new();
for id in &frontier {
let mut neighbours: Vec<(&String, &Edge)> = Vec::new();
for e in &graph.edges {
if !is_trusted_edge(e, walk_references, &graph.relation_profiles) {
continue;
}
if &e.from == id {
neighbours.push((&e.to, e));
} else if &e.to == id {
neighbours.push((&e.from, e));
}
}
neighbours.sort_by(|(a_id, a_edge), (b_id, b_edge)| {
walk_neighbour_score(
id,
b_id,
b_edge,
&terms,
&node_by_id_map,
&graph.relation_profiles,
)
.cmp(&walk_neighbour_score(
id,
a_id,
a_edge,
&terms,
&node_by_id_map,
&graph.relation_profiles,
))
.then_with(|| a_id.cmp(b_id))
});
for (other, e) in neighbours.into_iter().take(width) {
if edge_seen.insert((e.from.clone(), e.to.clone(), e.relation.clone())) {
edges.push(e.clone());
}
if visited.insert(other.clone()) {
next.push(other.clone());
}
}
}
if next.is_empty() {
break;
}
frontier = next;
}
let seed_set: HashSet<&String> = seed_ids.iter().collect();
let mut expanded_all: Vec<String> = visited
.iter()
.filter(|id| !seed_set.contains(id))
.cloned()
.collect();
expanded_all.sort();
edges.sort_by(|a, b| (&a.from, &a.to, &a.relation).cmp(&(&b.from, &b.to, &b.relation)));
let compiled = compile_context_pack(
graph,
&route,
&seeds,
&expanded_all,
&edges,
&terms,
DEFAULT_CONTEXT_BUDGET,
);
let compound = detect_compound_subgraph(graph, &route, &hits, &edges, &terms);
let selected: HashSet<&String> = compiled.context_order.iter().collect();
edges.retain(|edge| selected.contains(&edge.from) && selected.contains(&edge.to));
let selected_seed_ids = seed_ids.iter().collect::<HashSet<_>>();
let expanded = compiled
.context_order
.iter()
.filter(|id| !selected_seed_ids.contains(*id))
.cloned()
.collect();
Subgraph {
hits,
expanded,
edges,
route,
context_order: compiled.context_order,
compound,
omitted_context: compiled.omitted,
omitted_context_candidates: compiled.omitted_candidates,
}
}
fn detect_compound_subgraph(
graph: &Graph,
route: &str,
hits: &[Hit],
edges: &[Edge],
terms: &[String],
) -> Option<CompoundSubgraph> {
let terms = compound_terms(terms);
if terms.len() < 2 {
return None;
}
let node_by_id: HashMap<&str, &Node> = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect();
let mut anchors = hits
.iter()
.filter_map(|hit| {
let node = node_by_id.get(hit.id.as_str())?;
if matches!(node.kind, Kind::Section | Kind::Unknown) {
return None;
}
let matched_terms = matched_compound_terms(node, &terms);
if matched_terms.is_empty() {
return None;
}
let relevant = !matches!(hit.confidence, Confidence::Fallback)
|| matched_terms.len() >= 2
|| hit.id == route;
relevant.then_some(CompoundAnchor {
node_id: hit.id.clone(),
matched_terms,
confidence: hit.confidence,
score: hit.score,
})
})
.collect::<Vec<_>>();
anchors.sort_by(|a, b| {
b.matched_terms
.len()
.cmp(&a.matched_terms.len())
.then_with(|| b.score.cmp(&a.score))
.then_with(|| a.node_id.cmp(&b.node_id))
});
let mut covered = HashSet::new();
let mut selected = Vec::new();
let mut omitted_anchors = Vec::new();
for anchor in anchors {
let adds_new_term = anchor
.matched_terms
.iter()
.any(|term| !covered.contains(term.as_str()));
if !adds_new_term && anchor.node_id != route {
record_omitted_anchor(&mut omitted_anchors, anchor, "covered_terms");
continue;
}
if selected.len() >= 6 {
record_omitted_anchor(&mut omitted_anchors, anchor, "anchor_limit");
continue;
}
for term in &anchor.matched_terms {
covered.insert(term.clone());
}
selected.push(anchor);
}
if selected.len() < 2 {
return None;
}
let distinct_anchor_terms = selected
.iter()
.flat_map(|anchor| anchor.matched_terms.iter().cloned())
.collect::<HashSet<_>>();
if distinct_anchor_terms.len() < 2 {
return None;
}
let primary = select_compound_primary(graph, route, &selected, edges, &node_by_id);
let missing_terms = terms
.iter()
.filter(|term| !distinct_anchor_terms.contains(*term))
.cloned()
.collect::<Vec<_>>();
let coverage = distinct_anchor_terms.len() as f64 / terms.len() as f64;
Some(CompoundSubgraph {
primary,
anchors: selected,
omitted_anchors,
coverage,
missing_terms,
})
}
fn record_omitted_anchor(
out: &mut Vec<CompoundOmittedAnchor>,
anchor: CompoundAnchor,
reason: &str,
) {
if out.len() >= MAX_OMITTED_CONTEXT_CANDIDATES {
return;
}
out.push(CompoundOmittedAnchor {
node_id: anchor.node_id,
matched_terms: anchor.matched_terms,
confidence: anchor.confidence,
score: anchor.score,
reason: reason.to_string(),
});
}
fn compound_terms(terms: &[String]) -> Vec<String> {
let mut seen = HashSet::new();
terms
.iter()
.filter(|term| !is_compound_control_term(term))
.filter(|term| seen.insert((*term).clone()))
.cloned()
.collect()
}
fn is_compound_control_term(term: &str) -> bool {
matches!(
term,
"combine"
| "compared"
| "comparing"
| "comparison"
| "create"
| "draft"
| "explain"
| "from"
| "generate"
| "make"
| "prepare"
| "produce"
| "risk"
| "summarize"
| "anchor"
| "anchors"
| "candidate"
| "candidates"
| "target"
| "targets"
| "year"
| "versus"
| "vs"
| "write"
)
}
fn matched_compound_terms(node: &Node, terms: &[String]) -> Vec<String> {
let text = normalized_route_text(&format!(
"{} {} {} {} {}",
node.id,
node.title,
node.summary,
node.aliases.join(" "),
node.query_examples.join(" ")
));
terms
.iter()
.filter(|term| text.contains(normalized_route_text(term).as_str()))
.cloned()
.collect()
}
fn select_compound_primary(
graph: &Graph,
route: &str,
anchors: &[CompoundAnchor],
edges: &[Edge],
node_by_id: &HashMap<&str, &Node>,
) -> String {
let anchor_ids = anchors
.iter()
.map(|anchor| anchor.node_id.as_str())
.collect::<HashSet<_>>();
if node_by_id
.get(route)
.is_some_and(|node| matches!(node.kind, Kind::Agent | Kind::Skill))
&& reachable_anchor_count(route, &anchor_ids, edges) >= 2
{
return route.to_string();
}
graph
.nodes
.iter()
.filter(|node| matches!(node.kind, Kind::Skill | Kind::Agent))
.filter_map(|node| {
let reachable = reachable_anchor_count(&node.id, &anchor_ids, edges);
(reachable >= 2).then_some((node, reachable))
})
.max_by(|(a_node, a_reachable), (b_node, b_reachable)| {
a_reachable
.cmp(b_reachable)
.then_with(|| {
compound_primary_kind_priority(a_node.kind)
.cmp(&compound_primary_kind_priority(b_node.kind))
})
.then_with(|| b_node.id.cmp(&a_node.id))
})
.map_or_else(|| route.to_string(), |(node, _)| node.id.clone())
}
fn reachable_anchor_count(start: &str, anchor_ids: &HashSet<&str>, edges: &[Edge]) -> usize {
let mut reached = HashSet::new();
if anchor_ids.contains(start) {
reached.insert(start.to_string());
}
let mut visited = HashSet::from([start.to_string()]);
let mut frontier = vec![start.to_string()];
for _ in 0..2 {
let mut next = Vec::new();
for id in &frontier {
for edge in edges {
let other = if edge.from == *id {
Some(edge.to.as_str())
} else if edge.to == *id {
Some(edge.from.as_str())
} else {
None
};
let Some(other) = other else {
continue;
};
if anchor_ids.contains(other) {
reached.insert(other.to_string());
}
if visited.insert(other.to_string()) {
next.push(other.to_string());
}
}
}
frontier = next;
if frontier.is_empty() {
break;
}
}
reached.len()
}
fn compound_primary_kind_priority(kind: Kind) -> usize {
match kind {
Kind::Skill => 3,
Kind::Agent => 2,
_ => 0,
}
}
fn walk_neighbour_score(
current_id: &str,
id: &str,
edge: &Edge,
terms: &[String],
node_by_id: &HashMap<&str, &Node>,
profiles: &std::collections::BTreeMap<String, RelationProfile>,
) -> i64 {
let kind_score = node_by_id.get(id).map_or(0, |node| match node.kind {
Kind::Type | Kind::Trait => 30,
Kind::Function => 16,
Kind::Module => 12,
Kind::Skill | Kind::Agent => 10,
Kind::Doc | Kind::Section => 8,
Kind::Unknown => 0,
});
relation_context_score(&edge.relation, profiles)
+ relation_query_match_score(current_id, edge, terms, profiles)
+ node_query_support_score(id, terms, node_by_id)
+ kind_score
+ container_child_walk_bonus(current_id, edge, node_by_id)
}
fn container_child_walk_bonus(
current_id: &str,
edge: &Edge,
node_by_id: &HashMap<&str, &Node>,
) -> i64 {
if edge.from != current_id {
return 0;
}
let Some(current) = node_by_id.get(current_id) else {
return 0;
};
match (current.kind, edge.relation.as_str()) {
(
Kind::Doc | Kind::Section | Kind::Skill | Kind::Agent | Kind::Module | Kind::Type,
crate::schema::relation::CONTAINS | crate::schema::relation::HAS_KNOWLEDGE,
) => 34,
_ => 0,
}
}
fn is_trusted_edge(
edge: &Edge,
walk_references: bool,
profiles: &std::collections::BTreeMap<String, RelationProfile>,
) -> bool {
use crate::schema::relation::{MENTIONS, REFERENCES};
let profile = profiles
.get(&edge.relation)
.cloned()
.or_else(|| core_relation_profile(&edge.relation));
if profile.as_ref().is_some_and(|profile| !profile.traversable) {
return false;
}
match edge.relation.as_str() {
MENTIONS => false,
REFERENCES => walk_references && edge.basis == EdgeBasis::Resolved,
_ => edge.basis == EdgeBasis::Resolved,
}
}
fn should_walk_references(graph: &Graph, terms: &[String], top_hit: Option<&Hit>) -> bool {
if terms.len() > 1 {
return true;
}
let Some(hit) = top_hit else {
return false;
};
if matches!(hit.confidence, Confidence::Weak | Confidence::Fallback) {
return false;
}
let Some(node) = graph.nodes.iter().find(|node| node.id == hit.id) else {
return false;
};
matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
) && is_distinct_code_symbol_name(&node.title.to_lowercase())
}
fn should_seed_hit(
graph: &Graph,
hit: &Hit,
route: &str,
route_is_anchored: bool,
terms: &[String],
) -> bool {
if hit.id == route || !route_is_anchored {
return true;
}
node_by_id(graph, &hit.id).is_some_and(|node| {
if matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
) {
route_term_count(node, terms) > 0
} else {
support_term_count(node, terms) > 0
}
})
}
fn is_high_value_support_seed(graph: &Graph, hit: &Hit, terms: &[String]) -> bool {
node_by_id(graph, &hit.id).is_some_and(|node| {
has_receipt(node)
&& match node.kind {
Kind::Doc | Kind::Section | Kind::Skill | Kind::Agent => {
support_term_count(node, terms) >= 2
}
Kind::Function | Kind::Type | Kind::Trait | Kind::Module | Kind::Unknown => false,
}
})
}
fn is_connected_code_support_seed(
graph: &Graph,
hit: &Hit,
terms: &[String],
seed_ids: &HashSet<&str>,
walk_references: bool,
) -> bool {
let Some(node) = node_by_id(graph, &hit.id) else {
return false;
};
if !matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
) || !has_receipt(node)
|| support_term_count(node, terms) < 2
{
return false;
}
graph.edges.iter().any(|edge| {
is_trusted_edge(edge, walk_references, &graph.relation_profiles)
&& ((edge.from == hit.id && seed_ids.contains(edge.to.as_str()))
|| (edge.to == hit.id && seed_ids.contains(edge.from.as_str())))
})
}
fn select_subgraph_route(hits: &[Hit], graph: &Graph, terms: &[String]) -> String {
let Some(top) = hits.first() else {
return String::new();
};
if top.relation_anchor {
return top.id.clone();
}
if hit_has_exact_query_example(top)
&& node_by_id(graph, &top.id).is_some_and(|node| route_term_count(node, terms) > 0)
{
return top.id.clone();
}
if let Some(code_route) = select_code_intent_route(hits, graph, terms) {
return code_route;
}
if matches!(
top.confidence,
Confidence::Exact | Confidence::Strong | Confidence::Ambiguous
) && node_by_id(graph, &top.id).is_some_and(|node| route_term_count(node, terms) > 0)
{
return top.id.clone();
}
let node_by_id: HashMap<&str, &Node> = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect();
hits.iter()
.filter_map(|hit| {
let node = node_by_id.get(hit.id.as_str())?;
let route_hits = route_term_count(node, terms);
(route_hits > 0).then_some((hit, route_hits))
})
.max_by(|(a_hit, a_route_hits), (b_hit, b_route_hits)| {
a_route_hits
.cmp(b_route_hits)
.then_with(|| a_hit.score.cmp(&b_hit.score))
.then_with(|| b_hit.id.cmp(&a_hit.id))
})
.map_or_else(|| top.id.clone(), |(hit, _)| hit.id.clone())
}
fn select_code_intent_route(hits: &[Hit], graph: &Graph, terms: &[String]) -> Option<String> {
if !query_has_code_action_intent(terms) {
return None;
}
let subject_terms = code_subject_terms(terms);
if subject_terms.is_empty() {
return None;
}
let node_by_id: HashMap<&str, &Node> = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect();
let candidates = hits
.iter()
.filter_map(|hit| {
let node = node_by_id.get(hit.id.as_str())?;
if !is_code_route_kind(node.kind) {
return None;
}
let subject_hits = route_term_count(node, &subject_terms);
let exact_identifier_hits = code_identifier_term_count(node, &subject_terms);
(subject_hits > 0).then_some((hit, *node, exact_identifier_hits, subject_hits))
})
.collect::<Vec<_>>();
let min_score = candidates
.iter()
.map(|(hit, _, _, _)| hit.score)
.max()
.map(|score| ((score as f64) * 0.8).ceil() as i64)
.unwrap_or_default();
candidates
.into_iter()
.filter(|(hit, _, _, _)| hit.score >= min_score)
.max_by(
|(a_hit, a_node, a_exact_identifier_hits, a_subject_hits),
(b_hit, b_node, b_exact_identifier_hits, b_subject_hits)| {
a_exact_identifier_hits
.cmp(b_exact_identifier_hits)
.then_with(|| a_subject_hits.cmp(b_subject_hits))
.then_with(|| a_hit.score.cmp(&b_hit.score))
.then_with(|| a_hit.lexical_score.cmp(&b_hit.lexical_score))
.then_with(|| {
code_route_kind_priority(a_node.kind)
.cmp(&code_route_kind_priority(b_node.kind))
})
.then_with(|| b_hit.id.cmp(&a_hit.id))
},
)
.map(|(hit, _, _, _)| hit.id.clone())
}
fn hit_has_exact_query_example(hit: &Hit) -> bool {
hit.why.iter().any(|why| why == "exact query_example match")
}
fn code_identifier_term_count(node: &Node, terms: &[String]) -> usize {
let title = normalized_route_text(&node.title);
let id_tail = node
.id
.rsplit("::")
.next()
.map(normalized_route_text)
.unwrap_or_default();
terms
.iter()
.map(|term| normalized_route_text(term))
.filter(|term| !term.is_empty() && (term == &title || term == &id_tail))
.count()
}
fn query_has_code_action_intent(terms: &[String]) -> bool {
terms.iter().any(|term| {
matches!(
term.as_str(),
"call"
| "caller"
| "callee"
| "change"
| "changing"
| "code"
| "definition"
| "function"
| "implement"
| "implementation"
| "implemented"
| "method"
| "modify"
| "source"
| "trace"
| "type"
| "usage"
| "use"
| "used"
| "uses"
)
})
}
fn code_subject_terms(terms: &[String]) -> Vec<String> {
terms
.iter()
.filter(|term| !is_code_intent_control_term(term))
.cloned()
.collect()
}
fn is_code_intent_control_term(term: &str) -> bool {
matches!(
term,
"call"
| "caller"
| "callee"
| "change"
| "changing"
| "code"
| "definition"
| "function"
| "implement"
| "implementation"
| "implemented"
| "method"
| "modify"
| "source"
| "trace"
| "type"
| "usage"
| "use"
| "used"
| "uses"
)
}
fn is_code_route_kind(kind: Kind) -> bool {
matches!(
kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
)
}
fn code_route_kind_priority(kind: Kind) -> usize {
match kind {
Kind::Function => 4,
Kind::Type | Kind::Trait => 3,
Kind::Module => 2,
_ => 0,
}
}
fn node_by_id<'a>(graph: &'a Graph, id: &str) -> Option<&'a Node> {
graph.nodes.iter().find(|node| node.id == id)
}
fn route_term_count(node: &Node, terms: &[String]) -> usize {
let title = normalized_route_text(&node.title);
let alias_hits = if matches!(
node.kind,
Kind::Function | Kind::Type | Kind::Trait | Kind::Module
) {
0
} else {
node.aliases
.iter()
.map(|alias| normalized_route_text(alias))
.flat_map(|alias| {
let alias = alias.clone();
terms
.iter()
.filter(move |term| alias.contains(normalized_route_text(term).as_str()))
})
.count()
};
terms
.iter()
.filter(|term| title.contains(normalized_route_text(term).as_str()))
.count()
+ alias_hits
}
fn support_term_count(node: &Node, terms: &[String]) -> usize {
let mut count = route_term_count(node, terms);
let supporting_text = normalized_route_text(&format!(
"{} {} {}",
node.summary,
node.query_examples.join(" "),
node.tags.join(" ")
));
count += terms
.iter()
.filter(|term| supporting_text.contains(normalized_route_text(term).as_str()))
.count();
count
}
fn normalized_route_text(text: &str) -> String {
text.chars()
.filter(char::is_ascii_alphanumeric)
.flat_map(char::to_lowercase)
.collect()
}
#[derive(Debug, Clone)]
struct CompiledContextPack {
context_order: Vec<String>,
omitted: usize,
omitted_candidates: Vec<OmittedContextCandidate>,
}
fn compile_context_pack(
graph: &Graph,
route: &str,
seeds: &[&Hit],
expanded: &[String],
edges: &[Edge],
terms: &[String],
budget: usize,
) -> CompiledContextPack {
if budget == 0 {
return CompiledContextPack {
context_order: Vec::new(),
omitted: seeds.len() + expanded.len(),
omitted_candidates: seeds
.iter()
.map(|hit| OmittedContextCandidate {
node_id: hit.id.clone(),
score: hit.score,
reason: "pack_budget".to_string(),
})
.chain(expanded.iter().map(|id| OmittedContextCandidate {
node_id: id.clone(),
score: 0,
reason: "pack_budget".to_string(),
}))
.take(MAX_OMITTED_CONTEXT_CANDIDATES)
.collect(),
};
}
let node_by_id: HashMap<&str, &Node> = graph
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect();
let mut seen = HashSet::new();
let mut context_order = Vec::new();
let mut omitted_candidates = Vec::new();
let mut per_source: HashMap<String, usize> = HashMap::new();
let mut seed_order: Vec<&Hit> = seeds.to_vec();
seed_order.sort_by(|a, b| {
(b.id == route).cmp(&(a.id == route)).then_with(|| {
b.lexical_score
.cmp(&a.lexical_score)
.then_with(|| a.id.cmp(&b.id))
})
});
for (index, hit) in seed_order.into_iter().enumerate() {
if index >= MAX_SEED_CONTEXT {
record_omitted_candidate(&mut omitted_candidates, &hit.id, hit.score, "seed_limit");
continue;
}
let result = push_context_item(
&mut context_order,
&mut seen,
&mut per_source,
&node_by_id,
&hit.id,
budget,
true,
);
if let PushContextResult::Omitted(reason) = result {
record_omitted_candidate(&mut omitted_candidates, &hit.id, hit.score, reason);
}
}
let selected_seed_ids = context_order.iter().cloned().collect::<HashSet<_>>();
let mut candidates = expanded
.iter()
.filter(|id| !seen.contains(id.as_str()))
.map(|id| {
let score = context_candidate_score(
id,
&selected_seed_ids,
edges,
terms,
&node_by_id,
&graph.relation_profiles,
);
(id.clone(), score)
})
.collect::<Vec<_>>();
candidates.sort_by(|(a_id, a_score), (b_id, b_score)| {
b_score.cmp(a_score).then_with(|| a_id.cmp(b_id))
});
for (id, score) in candidates {
if context_order.len() >= budget {
record_omitted_candidate(&mut omitted_candidates, &id, score, "pack_budget");
break;
}
let result = push_context_item(
&mut context_order,
&mut seen,
&mut per_source,
&node_by_id,
&id,
budget,
false,
);
if let PushContextResult::Omitted(reason) = result {
record_omitted_candidate(&mut omitted_candidates, &id, score, reason);
}
}
let total_candidates = seeds.len() + expanded.len();
CompiledContextPack {
omitted: total_candidates.saturating_sub(context_order.len()),
context_order,
omitted_candidates,
}
}
enum PushContextResult {
Selected,
Omitted(&'static str),
}
fn push_context_item(
out: &mut Vec<String>,
seen: &mut HashSet<String>,
per_source: &mut HashMap<String, usize>,
node_by_id: &HashMap<&str, &Node>,
id: &str,
budget: usize,
force: bool,
) -> PushContextResult {
if out.len() >= budget || seen.contains(id) {
return PushContextResult::Omitted("pack_budget");
}
if !force && !node_by_id.get(id).is_some_and(|node| has_receipt(node)) {
return PushContextResult::Omitted("missing_receipt");
}
let source_key = node_by_id
.get(id)
.map_or_else(|| "(unknown)".to_string(), |node| source_key(node));
let count = per_source.get(&source_key).copied().unwrap_or(0);
if !force && count >= MAX_ITEMS_PER_SOURCE {
return PushContextResult::Omitted("source_cap");
}
seen.insert(id.to_string());
*per_source.entry(source_key).or_default() += 1;
out.push(id.to_string());
PushContextResult::Selected
}
fn record_omitted_candidate(
out: &mut Vec<OmittedContextCandidate>,
node_id: &str,
score: i64,
reason: &str,
) {
if out.len() >= MAX_OMITTED_CONTEXT_CANDIDATES {
return;
}
if out.iter().any(|candidate| candidate.node_id == node_id) {
return;
}
out.push(OmittedContextCandidate {
node_id: node_id.to_string(),
score,
reason: reason.to_string(),
});
}
fn has_receipt(node: &Node) -> bool {
node.span.is_some() || !node.source_files.is_empty()
}
fn context_candidate_score(
id: &str,
selected_seed_ids: &HashSet<String>,
edges: &[Edge],
terms: &[String],
node_by_id: &HashMap<&str, &Node>,
profiles: &std::collections::BTreeMap<String, RelationProfile>,
) -> i64 {
let mut score = node_by_id
.get(id)
.map_or(0, |node| kind_context_score(node.kind));
for edge in edges {
let touches_candidate = edge.from == id || edge.to == id;
if !touches_candidate {
continue;
}
let other = if edge.from == id {
&edge.to
} else {
&edge.from
};
let relation_score = relation_context_score(&edge.relation, profiles);
let relation_query_score = relation_query_match_score(other, edge, terms, profiles);
if selected_seed_ids.contains(other) {
score += relation_score + relation_query_score + 40;
} else {
score += relation_score + relation_query_score;
}
}
score += node_query_support_score(id, terms, node_by_id);
score
}
fn node_query_support_score(id: &str, terms: &[String], node_by_id: &HashMap<&str, &Node>) -> i64 {
let Some(node) = node_by_id.get(id) else {
return 0;
};
let support = support_term_count(node, terms) as i64;
if support == 0 {
return 0;
}
let multiplier = match node.kind {
Kind::Function | Kind::Type | Kind::Trait | Kind::Module => 14,
Kind::Doc | Kind::Section | Kind::Skill | Kind::Agent => 10,
Kind::Unknown => 0,
};
support * multiplier
}
fn relation_query_match_score(
current_id: &str,
edge: &Edge,
terms: &[String],
profiles: &std::collections::BTreeMap<String, RelationProfile>,
) -> i64 {
if terms.is_empty() {
return 0;
}
let phrase = if edge.from == current_id {
relation_phrase(&edge.relation, profiles)
} else if edge.to == current_id {
reverse_relation_phrase(&edge.relation, profiles)
} else {
return 0;
};
let phrase_terms = terms_of(&phrase.to_lowercase());
if phrase_terms.is_empty() {
return 0;
}
let matched = phrase_terms
.iter()
.filter(|phrase_term| terms.iter().any(|term| term == *phrase_term))
.count();
if matched == 0 {
return 0;
}
if matched == phrase_terms.len() {
36
} else {
(matched as i64) * 12
}
}
fn kind_context_score(kind: Kind) -> i64 {
match kind {
Kind::Function | Kind::Type | Kind::Trait => 16,
Kind::Module => 10,
Kind::Skill | Kind::Agent => 8,
Kind::Doc | Kind::Section => 6,
Kind::Unknown => 0,
}
}
fn relation_context_score(
relation: &str,
profiles: &std::collections::BTreeMap<String, RelationProfile>,
) -> i64 {
if let Some(profile) = profiles.get(relation) {
return profile.context_score;
}
if let Some(profile) = core_relation_profile(relation) {
return profile.context_score;
}
match relation {
"depends_on" => 30,
"produces" => 30,
"consumes" => 28,
"requires_contract" => 22,
"uses_tool" => 22,
"dispatches" => 22,
"related_to" => 18,
_ => 4,
}
}
fn source_key(node: &Node) -> String {
if let Some(span) = &node.span {
return span.path.clone();
}
node.source_files
.first()
.cloned()
.unwrap_or_else(|| node.id.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn focus_walks_resolved_references_but_not_lexical_references() {
let graph = Graph {
nodes: vec![
test_node("fn.config::load_settings", "load_settings", Kind::Function),
test_node(
"fn.worker::apply_settings",
"apply_settings",
Kind::Function,
),
test_node("fn.local::maybe_settings", "maybe_settings", Kind::Function),
],
edges: vec![
Edge {
from: "fn.worker::apply_settings".to_string(),
to: "fn.config::load_settings".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved reference".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "fn.local::maybe_settings".to_string(),
to: "fn.config::load_settings".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "local lexical reference".to_string(),
basis: EdgeBasis::Lexical,
..Default::default()
},
],
..Default::default()
};
let sg = ground_subgraph(&graph, "where is load_settings used", 1, 1, 8);
assert!(
sg.expanded
.contains(&"fn.worker::apply_settings".to_string())
);
assert!(
!sg.expanded
.contains(&"fn.local::maybe_settings".to_string())
);
}
#[test]
fn focus_context_is_budgeted_but_keeps_high_value_neighbours() {
let mut nodes = vec![
test_node("fn.config::load_settings", "load_settings", Kind::Function),
test_node("type.config::Settings", "Settings", Kind::Type),
];
let mut edges = vec![Edge {
from: "fn.config::load_settings".to_string(),
to: "type.config::Settings".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved type reference".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}];
for i in 0..80 {
let id = format!("fn.noise::helper_{i}");
nodes.push(test_node(&id, &format!("helper_{i}"), Kind::Function));
edges.push(Edge {
from: "fn.config::load_settings".to_string(),
to: id,
relation: crate::schema::relation::CONTAINS.to_string(),
evidence: "wide container fanout".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
});
}
let graph = Graph {
nodes,
edges,
..Default::default()
};
let sg = ground_subgraph(&graph, "load_settings", 1, 1, 100);
assert!(sg.context_order.len() <= DEFAULT_CONTEXT_BUDGET);
assert!(sg.omitted_context > 0);
assert!(
sg.context_order
.contains(&"type.config::Settings".to_string()),
"budgeting should preserve high-value resolved neighbours"
);
assert!(
sg.omitted_context_candidates
.iter()
.any(|candidate| candidate.reason == "pack_budget"
&& candidate.node_id.starts_with("fn.noise::helper_")),
"bounded focus should expose which candidate lost the pack budget: {:#?}",
sg.omitted_context_candidates
);
}
#[test]
fn focus_walk_prioritizes_type_references_over_lexical_neighbour_order() {
let mut nodes = vec![
test_node(
"fn.engine::run_package_evals",
"run_package_evals",
Kind::Function,
),
test_node(
"type.engine::PackageEvalReport",
"PackageEvalReport",
Kind::Type,
),
];
let mut edges = vec![Edge {
from: "fn.engine::run_package_evals".to_string(),
to: "type.engine::PackageEvalReport".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved return type".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}];
for i in 0..12 {
let id = format!("fn.alpha::helper_{i}");
nodes.push(test_node(&id, &format!("helper_{i}"), Kind::Function));
edges.push(Edge {
from: "fn.engine::run_package_evals".to_string(),
to: id,
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved helper call".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
});
}
let graph = Graph {
nodes,
edges,
..Default::default()
};
let sg = ground_subgraph(&graph, "run_package_evals", 1, 1, 4);
assert!(
sg.context_order
.contains(&"type.engine::PackageEvalReport".to_string()),
"bounded expansion should keep the directly referenced report type"
);
}
#[test]
fn focus_walk_prioritizes_container_children_before_external_references() {
let nodes = vec![
test_node("type.policy::RetryPolicy", "RetryPolicy", Kind::Type),
test_node(
"fn.policy::RetryPolicy::should_retry",
"should_retry",
Kind::Function,
),
test_node("type.policy::Backoff", "Backoff", Kind::Type),
test_node("fn.queue::Task::new", "new", Kind::Function),
test_node(
"fn.scheduler::Scheduler::submit_fire_and_forget",
"submit_fire_and_forget",
Kind::Function,
),
];
let edges = vec![
Edge {
from: "type.policy::RetryPolicy".to_string(),
to: "fn.policy::RetryPolicy::should_retry".to_string(),
relation: crate::schema::relation::CONTAINS.to_string(),
evidence: "method".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "type.policy::RetryPolicy".to_string(),
to: "type.policy::Backoff".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "field type".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "fn.queue::Task::new".to_string(),
to: "type.policy::RetryPolicy".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "constructor argument".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "fn.scheduler::Scheduler::submit_fire_and_forget".to_string(),
to: "type.policy::RetryPolicy".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "policy constructor".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
];
let graph = Graph {
nodes,
edges,
..Default::default()
};
let sg = ground_subgraph(&graph, "RetryPolicy", 1, 1, 2);
assert!(
sg.context_order
.contains(&"fn.policy::RetryPolicy::should_retry".to_string()),
"bounded expansion from a type should keep its own methods before external references"
);
}
#[test]
fn focus_pack_skips_unreceipted_expanded_nodes() {
let graph = Graph {
nodes: vec![
test_node("fn.config::load_settings", "load_settings", Kind::Function),
Node {
id: "trait.Default".to_string(),
kind: Kind::Trait,
subkind: None,
title: "Default".to_string(),
summary: String::new(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: Vec::new(),
span: None,
partition: None,
},
],
edges: vec![Edge {
from: "fn.config::load_settings".to_string(),
to: "trait.Default".to_string(),
relation: crate::schema::relation::IMPLEMENTS.to_string(),
evidence: "synthetic trait implementation".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
..Default::default()
};
let sg = ground_subgraph(&graph, "load_settings", 1, 1, 8);
assert!(
!sg.context_order.contains(&"trait.Default".to_string()),
"expanded task context should stay receipt-backed"
);
assert!(sg.omitted_context > 0);
assert!(
sg.omitted_context_candidates
.iter()
.any(|candidate| candidate.node_id == "trait.Default"
&& candidate.reason == "missing_receipt"),
"focus should explain receipt-backed omissions: {:#?}",
sg.omitted_context_candidates
);
}
#[test]
fn focus_explains_source_cap_omissions() {
let mut nodes = vec![test_node("doc.route", "Route", Kind::Doc)];
let mut edges = Vec::new();
for i in 0..12 {
let id = format!("doc.route.section-{i}");
nodes.push(Node {
id: id.clone(),
kind: Kind::Section,
subkind: None,
title: format!("Route Section {i}"),
summary: "Route support section.".to_string(),
aliases: Vec::new(),
tags: Vec::new(),
query_examples: Vec::new(),
source_files: vec!["fixtures/route.md".to_string()],
span: Some(crate::schema::Span {
path: "fixtures/route.md".to_string(),
start_line: i + 1,
end_line: i + 1,
}),
partition: None,
});
edges.push(Edge {
from: "doc.route".to_string(),
to: id,
relation: crate::schema::relation::CONTAINS.to_string(),
evidence: "section".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
});
}
let graph = Graph {
nodes,
edges,
..Default::default()
};
let sg = ground_subgraph(&graph, "Route", 1, 1, 20);
assert!(
sg.omitted_context_candidates
.iter()
.any(|candidate| candidate.reason == "source_cap"
&& candidate.node_id.starts_with("doc.route.section-")),
"focus should explain when same-source candidates are capped: {:#?}",
sg.omitted_context_candidates
);
}
#[test]
fn relation_profiles_can_disable_focus_traversal() {
let graph = Graph {
nodes: vec![
test_node("doc.seed", "Seed", Kind::Doc),
test_node("doc.external", "External", Kind::Doc),
],
edges: vec![Edge {
from: "doc.seed".to_string(),
to: "doc.external".to_string(),
relation: "catalogs".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
relation_profiles: [(
"catalogs".to_string(),
RelationProfile {
forward_phrase: "catalogs".to_string(),
reverse_phrase: "cataloged by".to_string(),
context_score: 30,
searchable: true,
traversable: false,
domain_kinds: Vec::new(),
range_kinds: Vec::new(),
},
)]
.into_iter()
.collect(),
};
let sg = ground_subgraph(&graph, "Seed", 1, 1, 8);
assert!(!sg.context_order.contains(&"doc.external".to_string()));
}
#[test]
fn focus_walk_prefers_query_supporting_code_neighbour_when_width_is_tight() {
let graph = Graph {
nodes: vec![
test_node(
"fn.claims::Engine::aether_claim_state",
"aether_claim_state",
Kind::Function,
),
test_node_with_summary(
"fn.claims::State::ledger_parse_errors",
"ledger_parse_errors",
Kind::Function,
"Count of failed ledger lines; a broken ledger leaves the claim bridge blind.",
),
test_node(
"fn.claims::Engine::vault_root",
"vault_root",
Kind::Function,
),
],
edges: vec![
Edge {
from: "fn.claims::Engine::aether_claim_state".to_string(),
to: "fn.claims::State::ledger_parse_errors".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved reference".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "fn.claims::Engine::aether_claim_state".to_string(),
to: "fn.claims::Engine::vault_root".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved reference".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
],
..Default::default()
};
let sg = ground_subgraph(&graph, "debug claim ledger aether_claim_state", 1, 1, 1);
assert!(
sg.context_order
.contains(&"fn.claims::State::ledger_parse_errors".to_string()),
"task-matching code support should beat generic code neighbours under tight width"
);
}
#[test]
fn connected_code_support_hit_can_seed_without_title_match() {
let graph = Graph {
nodes: vec![
test_node(
"fn.claims::Engine::aether_claim_state",
"aether_claim_state",
Kind::Function,
),
test_node_with_summary(
"fn.claims::State::ledger_parse_errors",
"ledger_parse_errors",
Kind::Function,
"Count of failed ledger lines; a broken ledger leaves the claim bridge blind.",
),
test_node_with_summary(
"fn.misc::run",
"run",
Kind::Function,
"Debug claim ledger helper with no relation to the route.",
),
],
edges: vec![Edge {
from: "fn.claims::Engine::aether_claim_state".to_string(),
to: "fn.claims::State::ledger_parse_errors".to_string(),
relation: crate::schema::relation::REFERENCES.to_string(),
evidence: "resolved reference".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
..Default::default()
};
let sg = ground_subgraph(&graph, "debug claim ledger aether_claim_state", 3, 0, 0);
assert!(
sg.context_order
.contains(&"fn.claims::State::ledger_parse_errors".to_string()),
"connected support code should seed beside the chosen route"
);
assert!(
!sg.context_order.contains(&"fn.misc::run".to_string()),
"unconnected body-only code should remain out of the seed set"
);
}
#[test]
fn focus_route_keeps_relation_anchored_source_over_named_target() {
let graph = Graph {
nodes: vec![
test_node("doc.contract", "Deploy Contract", Kind::Doc),
test_node(
"fn.release::check_deploy_readiness",
"check_deploy_readiness",
Kind::Function,
),
],
edges: vec![Edge {
from: "doc.contract".to_string(),
to: "fn.release::check_deploy_readiness".to_string(),
relation: "verifies".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
}],
relation_profiles: [(
"verifies".to_string(),
RelationProfile::new("verifies", "verified by", 30),
)]
.into_iter()
.collect(),
};
let sg = ground_subgraph(&graph, "what verifies check_deploy_readiness", 5, 1, 4);
assert_eq!(
sg.hits.first().map(|hit| hit.id.as_str()),
Some("doc.contract")
);
assert_eq!(
sg.route, "doc.contract",
"focus routing should preserve a typed relation anchor instead of rerouting to the named endpoint"
);
}
#[test]
fn focus_expansion_prefers_query_named_relation_when_width_is_tight() {
let graph = Graph {
nodes: vec![
test_node("doc.gate", "Release Gate", Kind::Doc),
test_node("doc.runbook", "Deploy Runbook", Kind::Doc),
test_node("fn.release::check", "check", Kind::Function),
],
edges: vec![
Edge {
from: "doc.gate".to_string(),
to: "doc.runbook".to_string(),
relation: "blocks".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "doc.gate".to_string(),
to: "fn.release::check".to_string(),
relation: "verifies".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
],
relation_profiles: [
(
"blocks".to_string(),
RelationProfile::new("blocks", "blocked by", 31),
),
(
"verifies".to_string(),
RelationProfile::new("verifies", "verified by", 29),
),
]
.into_iter()
.collect(),
};
let sg = ground_subgraph(&graph, "release gate verifies", 1, 1, 1);
assert!(
sg.context_order.contains(&"fn.release::check".to_string()),
"the relation named in the query should win bounded focus expansion"
);
assert!(
!sg.context_order.contains(&"doc.runbook".to_string()),
"a slightly higher static context_score should not beat the query's explicit relation intent"
);
}
#[test]
fn focus_route_prefers_title_anchored_fallback_over_body_only_noise() {
let graph = Graph {
nodes: vec![
test_node_with_summary(
"fn.tests::run",
"run",
Kind::Function,
"trace package enforcement eidos harness helper",
),
test_node_with_summary(
"fn.engine::check_eval_gates",
"check_eval_gates",
Kind::Function,
"enforces configured package gates",
),
],
edges: Vec::new(),
..Default::default()
};
let sg = ground_subgraph(&graph, "trace package eval gate enforcement eidos", 2, 1, 8);
assert_eq!(sg.route, "fn.engine::check_eval_gates");
assert_eq!(
sg.context_order.first().map(String::as_str),
Some("fn.engine::check_eval_gates")
);
assert!(
!sg.context_order.contains(&"fn.tests::run".to_string()),
"body-only fallback hits should stay in hits but not seed the focus pack"
);
}
#[test]
fn focus_route_prefers_code_anchor_for_code_action_query() {
let graph = Graph {
nodes: vec![
test_node_with_summary(
"skill.retry-policy",
"retry-policy",
Kind::Skill,
"Guide for retry flow changes and delay_ms safety.",
),
test_node_with_summary(
"fn.policy::RetryPolicy::delay_ms",
"delay_ms",
Kind::Function,
"Computes the retry delay.",
),
],
edges: Vec::new(),
..Default::default()
};
let hits = vec![
Hit {
id: "skill.retry-policy".to_string(),
score: 120,
lexical_score: 120,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
Hit {
id: "fn.policy::RetryPolicy::delay_ms".to_string(),
score: 60,
lexical_score: 60,
confidence: Confidence::Weak,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
];
let route = select_subgraph_route(
&hits,
&graph,
&[
"trace".to_string(),
"delayms".to_string(),
"used".to_string(),
"retry".to_string(),
"flow".to_string(),
],
);
assert_eq!(route, "fn.policy::RetryPolicy::delay_ms");
}
#[test]
fn code_action_route_prefers_explicit_identifier_over_related_helper_terms() {
let graph = Graph {
nodes: vec![
test_node(
"fn.billing::sync_invoice_batch",
"sync_invoice_batch",
Kind::Function,
),
test_node(
"fn.billing::build_idempotency_key",
"build_idempotency_key",
Kind::Function,
),
],
edges: Vec::new(),
..Default::default()
};
let hits = vec![
Hit {
id: "fn.billing::sync_invoice_batch".to_string(),
score: 81,
lexical_score: 81,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
Hit {
id: "fn.billing::build_idempotency_key".to_string(),
score: 36,
lexical_score: 64,
confidence: Confidence::Fallback,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 0.8,
relation_anchor: false,
},
];
let route = select_subgraph_route(
&hits,
&graph,
&[
"trace".to_string(),
"sync_invoice_batch".to_string(),
"build".to_string(),
"idempotency".to_string(),
"key".to_string(),
"billing".to_string(),
"sync".to_string(),
],
);
assert_eq!(route, "fn.billing::sync_invoice_batch");
}
#[test]
fn focus_route_keeps_exact_query_example_front_door_before_code_override() {
let mut doc = test_node("doc.mcp-prompts", "MCP Prompt Workflows", Kind::Doc);
doc.query_examples = vec!["how should coding agents use Eidos MCP prompts".to_string()];
let graph = Graph {
nodes: vec![
doc,
test_node(
"fn.eidos::mcp::start_coding_task",
"start_coding_task",
Kind::Function,
),
],
edges: Vec::new(),
..Default::default()
};
let hits = vec![
Hit {
id: "doc.mcp-prompts".to_string(),
score: 171,
lexical_score: 171,
confidence: Confidence::Ambiguous,
why: vec!["exact query_example match".to_string()],
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
Hit {
id: "fn.eidos::mcp::start_coding_task".to_string(),
score: 146,
lexical_score: 146,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
];
let route = select_subgraph_route(
&hits,
&graph,
&[
"code".to_string(),
"agent".to_string(),
"use".to_string(),
"eidos".to_string(),
"mcp".to_string(),
"prompt".to_string(),
],
);
assert_eq!(route, "doc.mcp-prompts");
}
#[test]
fn code_action_route_prefers_ranked_hit_before_loose_subject_count() {
let graph = Graph {
nodes: vec![
test_node(
"fn.eidos::mcp::EidosMcpServer::deep_code_review",
"deep_code_review",
Kind::Function,
),
test_node(
"fn.eidos::mcp_consent_guard::mcp_source_never_references_the_sigil_write_surface",
"mcp_source_never_references_the_sigil_write_surface",
Kind::Function,
),
],
edges: Vec::new(),
..Default::default()
};
let hits = vec![
Hit {
id: "fn.eidos::mcp::EidosMcpServer::deep_code_review".to_string(),
score: 160,
lexical_score: 160,
confidence: Confidence::Ambiguous,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
Hit {
id: "fn.eidos::mcp_consent_guard::mcp_source_never_references_the_sigil_write_surface"
.to_string(),
score: 123,
lexical_score: 123,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
];
let route = select_subgraph_route(
&hits,
&graph,
&[
"review".to_string(),
"eidos".to_string(),
"mcp".to_string(),
"prompt".to_string(),
"surface".to_string(),
"implementation".to_string(),
],
);
assert_eq!(route, "fn.eidos::mcp::EidosMcpServer::deep_code_review");
}
#[test]
fn high_value_receipted_docs_can_seed_beside_confident_code() {
let mut doc = test_node_with_summary(
"doc.billing",
"Billing Sync Runbook",
Kind::Doc,
"The idempotency key protects provider calls.",
);
doc.source_files.push("docs/billing.md".to_string());
let graph = Graph {
nodes: vec![doc],
edges: Vec::new(),
..Default::default()
};
let hit = Hit {
id: "doc.billing".to_string(),
score: 37,
lexical_score: 56,
confidence: Confidence::Fallback,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 0.5,
relation_anchor: false,
};
assert!(is_high_value_support_seed(
&graph,
&hit,
&[
"idempotency".to_string(),
"key".to_string(),
"billing".to_string(),
]
));
}
#[test]
fn code_action_route_requires_subject_match_not_just_control_word() {
let graph = Graph {
nodes: vec![
test_node("doc.retry-policy", "Retry Policy", Kind::Doc),
test_node("fn.misc::change", "change", Kind::Function),
],
edges: Vec::new(),
..Default::default()
};
let hits = vec![
Hit {
id: "doc.retry-policy".to_string(),
score: 80,
lexical_score: 80,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
Hit {
id: "fn.misc::change".to_string(),
score: 70,
lexical_score: 70,
confidence: Confidence::Strong,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 1.0,
relation_anchor: false,
},
];
let route = select_subgraph_route(
&hits,
&graph,
&[
"change".to_string(),
"retry".to_string(),
"policy".to_string(),
],
);
assert_eq!(route, "doc.retry-policy");
}
#[test]
fn route_term_matching_ignores_identifier_separators() {
let skill = test_node("skill.retry-policy", "retry-policy", Kind::Skill);
let type_node = test_node("type.policy::RetryPolicy", "RetryPolicy", Kind::Type);
assert_eq!(route_term_count(&skill, &["retrypolicy".to_string()]), 1);
assert_eq!(
route_term_count(&type_node, &["retry_policy".to_string()]),
1
);
}
#[test]
fn docs_can_seed_from_supporting_text_when_route_is_anchored() {
let graph = Graph {
nodes: vec![
test_node("skill.retry-policy", "retry-policy", Kind::Skill),
test_node_with_summary(
"doc.architecture",
"System Architecture",
Kind::Doc,
"The retry flow explains where delay_ms is used.",
),
],
edges: Vec::new(),
..Default::default()
};
let hit = Hit {
id: "doc.architecture".to_string(),
score: 30,
lexical_score: 30,
confidence: Confidence::Fallback,
why: Vec::new(),
relation_matches: Vec::new(),
anchor: 0.0,
relation_anchor: false,
};
assert!(should_seed_hit(
&graph,
&hit,
"skill.retry-policy",
true,
&["retry".to_string(), "flow".to_string()]
));
}
#[test]
fn compound_terms_ignore_vocabulary_target_control_words() {
let terms = vec![
"agent".to_string(),
"workflow".to_string(),
"target".to_string(),
"candidates".to_string(),
"anchor".to_string(),
];
assert_eq!(
compound_terms(&terms),
vec!["agent".to_string(), "workflow".to_string()]
);
}
#[test]
fn compound_focus_surfaces_multi_anchor_primary_skill() {
let graph = Graph {
nodes: vec![
test_node_with_summary(
"doc.forecast-2026",
"2026 Revenue Forecast",
Kind::Doc,
"Baseline forecast assumptions for fiscal year 2026.",
),
test_node_with_summary(
"doc.seattle-clients",
"Seattle Client Segment Map",
Kind::Doc,
"Seattle client segmentation and expansion probability.",
),
test_node_with_summary(
"skill.client-forecast",
"Client Forecast Analysis",
Kind::Skill,
"Combine forecast assumptions with client segment evidence.",
),
],
edges: vec![
Edge {
from: "skill.client-forecast".to_string(),
to: "doc.forecast-2026".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "skill.client-forecast".to_string(),
to: "doc.seattle-clients".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
],
..Default::default()
};
let sg = ground_subgraph(
&graph,
"forecast for year 2026 from Seattle clients",
5,
2,
4,
);
let compound = sg.compound.expect("multi-facet query should be compound");
assert_eq!(compound.primary, "skill.client-forecast");
assert!(compound.coverage >= 0.75, "compound={compound:#?}");
assert!(
compound
.anchors
.iter()
.any(|anchor| anchor.node_id == "doc.forecast-2026"
&& anchor.matched_terms.contains(&"2026".to_string()))
);
assert!(
compound
.anchors
.iter()
.any(|anchor| anchor.node_id == "doc.seattle-clients"
&& anchor.matched_terms.contains(&"seattle".to_string()))
);
}
#[test]
fn compound_focus_explains_covered_omitted_anchors() {
let graph = Graph {
nodes: vec![
test_node_with_summary(
"doc.forecast-2026",
"2026 Revenue Forecast",
Kind::Doc,
"Baseline forecast assumptions for fiscal year 2026.",
),
test_node_with_summary(
"doc.forecast-2026-alt",
"2026 Forecast Appendix",
Kind::Doc,
"Secondary forecast assumptions for fiscal year 2026.",
),
test_node_with_summary(
"doc.seattle-clients",
"Seattle Client Segment Map",
Kind::Doc,
"Seattle client segmentation and expansion probability.",
),
test_node_with_summary(
"skill.client-forecast",
"Client Forecast Analysis",
Kind::Skill,
"Combine forecast assumptions with client segment evidence.",
),
],
edges: vec![
Edge {
from: "skill.client-forecast".to_string(),
to: "doc.forecast-2026".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "skill.client-forecast".to_string(),
to: "doc.forecast-2026-alt".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
Edge {
from: "skill.client-forecast".to_string(),
to: "doc.seattle-clients".to_string(),
relation: "depends_on".to_string(),
evidence: "frontmatter".to_string(),
basis: EdgeBasis::Resolved,
..Default::default()
},
],
..Default::default()
};
let sg = ground_subgraph(
&graph,
"forecast for year 2026 from Seattle clients",
8,
2,
4,
);
let compound = sg.compound.expect("multi-facet query should be compound");
assert!(
compound
.omitted_anchors
.iter()
.any(|anchor| anchor.node_id == "doc.forecast-2026-alt"
&& anchor.reason == "covered_terms"
&& anchor.matched_terms.contains(&"2026".to_string())),
"compound focus should expose skipped duplicate-facet anchors: {compound:#?}"
);
}
fn test_node(id: &str, title: &str, kind: Kind) -> Node {
test_node_with_summary(id, title, kind, "")
}
fn test_node_with_summary(id: &str, title: &str, kind: Kind, 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!("fixtures/{title}.rs")],
span: None,
partition: None,
}
}
}