use super::*;
use crate::mcp::types::ResultUnit;
impl FriggMcpServer {
pub(in crate::mcp::server) async fn find_implementations_impl(
&self,
mut params: FindImplementationsParams,
) -> Result<Json<FindImplementationsResponse>, ErrorData> {
Self::validate_navigation_target_inputs(
params.target.as_ref(),
params.symbol.as_deref(),
params.path.as_deref(),
params.line,
params.column,
)?;
params.repository_id = self.navigation_target_repository_hint(
params.target.as_ref(),
params.repository_id.as_deref(),
)?;
self.find_implementations_for_resolved_target_impl(params, None)
.await
}
pub(in crate::mcp::server) async fn find_implementations_for_resolved_target_impl(
&self,
params: FindImplementationsParams,
resolved_target: Option<ResolvedNavigationTarget>,
) -> Result<Json<FindImplementationsResponse>, ErrorData> {
let page_limit = self.navigation_page_limit(params.limit)?;
let (resume_offset, continuation_binding) = self.navigation_continuation_context(
"find_implementations",
¶ms,
params.repository_id.clone(),
ResultUnit::Implementation,
)?;
let execution_context = self
.read_only_tool_execution_context("find_implementations", params.repository_id.clone());
let execution_context_for_blocking = execution_context.clone();
let resource_budgets = self.find_references_resource_budgets();
let params_for_blocking = params.clone();
let resolved_target_for_blocking = resolved_target.clone();
let server = self.clone();
let execution = self.run_read_only_tool_blocking(&execution_context, move || {
let mut scoped_repository_ids: Vec<String> = Vec::new();
let mut selected_symbol_id: Option<String> = None;
let mut selected_precise_symbol: Option<String> = None;
let mut resolution_precision: Option<String> = None;
let mut resolution_source: Option<String> = None;
let mut target_selection_candidate_count = 0usize;
let mut target_selection_same_rank_count = 0usize;
let mut effective_limit: Option<usize> = None;
let mut precise_artifacts_ingested = 0usize;
let mut precise_artifacts_failed = 0usize;
let mut match_count = 0usize;
let mut fallback_reason: Option<String> = None;
(|| -> Result<Json<FindImplementationsResponse>, ErrorData> {
let limit = usize::MAX;
let include_follow_up_structural =
params_for_blocking.include_follow_up_structural == Some(true);
effective_limit = Some(page_limit);
let freshness_basis = server
.scoped_read_only_tool_execution_context(
execution_context_for_blocking.tool_name,
execution_context_for_blocking.repository_hint.clone(),
RepositoryResponseCacheFreshnessMode::ManifestOnly,
)?
.cache_freshness
.basis;
let attach_freshness = |metadata| {
Self::metadata_with_freshness_basis(metadata, &freshness_basis)
};
let corpora = server.collect_repository_symbol_corpora(
params_for_blocking.repository_id.as_deref(),
)?;
scoped_repository_ids = corpora
.iter()
.map(|corpus| corpus.repository_id.clone())
.collect::<Vec<_>>();
let resolved_target = match resolved_target_for_blocking.clone() {
Some(resolved_target) => resolved_target,
None => server.resolve_navigation_request(
&corpora,
params_for_blocking.target.as_ref(),
params_for_blocking.symbol.as_deref(),
params_for_blocking.path.as_deref(),
params_for_blocking.line,
params_for_blocking.column,
params_for_blocking.repository_id.as_deref(),
)?,
};
resolution_source = Some(resolved_target.resolution_source.to_owned());
let symbol_query = resolved_target.symbol_query;
let target_selection = Some(Self::navigation_target_selection_summary_for_selection(
&corpora,
&symbol_query,
&resolved_target.selection,
resolved_target.resolution_source,
));
let target_resolution = match resolved_target.selection {
NavigationTargetSelection::Resolved(target_resolution) => target_resolution,
NavigationTargetSelection::DisambiguationRequired(_) => {
let metadata = json!({
"precision": "unavailable",
"heuristic": false,
"disambiguation_required": true,
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_summary_value(
target_selection
.as_ref()
.expect("target selection summary should be present"),
),
});
let (metadata, note) =
Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(FindImplementationsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::Implementation,
0,
Some(0),
NavigationMode::UnavailableNoPrecise,
false,
None,
),
matches: Vec::new(),
result_handle: None,
mode: NavigationMode::UnavailableNoPrecise,
target_selection,
metadata,
note,
recovery: RecoveryFields::default(),
}));
}
};
target_selection_candidate_count = target_resolution.candidate_count;
target_selection_same_rank_count =
target_resolution.selected_rank_candidate_count;
let target = target_resolution.candidate;
selected_symbol_id = Some(target.symbol.stable_id.clone());
let target_corpus = target_resolution.corpus;
let cached_precise_graph =
server.precise_graph_for_corpus(target_corpus.as_ref(), resource_budgets)?;
let precise_coverage = cached_precise_graph.coverage_mode;
let graph = cached_precise_graph.graph;
precise_artifacts_ingested = cached_precise_graph.ingest_stats.artifacts_ingested;
precise_artifacts_failed = cached_precise_graph.ingest_stats.artifacts_failed;
let precise_targets = Self::matching_precise_symbols_for_resolved_target(
graph.as_ref(),
&target_corpus.repository_id,
&target.root,
&symbol_query,
&target.symbol,
);
let mut precise_matches = Vec::new();
for precise_target in &precise_targets {
let matches = Self::precise_implementation_matches_for_symbol(
graph.as_ref(),
&target_corpus.repository_id,
&target.root,
precise_coverage,
precise_target,
);
if !matches.is_empty() {
selected_precise_symbol = Some(precise_target.symbol.clone());
precise_matches = matches;
break;
}
}
if precise_matches.is_empty() {
for precise_target in &precise_targets {
let matches = Self::precise_implementation_matches_from_occurrences(
graph.as_ref(),
target_corpus.as_ref(),
&target.root,
&target.symbol.name,
precise_coverage,
precise_target,
);
if !matches.is_empty() {
selected_precise_symbol = Some(precise_target.symbol.clone());
precise_matches = matches;
break;
}
}
}
if precise_matches.is_empty() {
precise_matches = Self::heuristic_implementation_matches_from_symbols(
&target.symbol,
target_corpus.as_ref(),
&target.root,
);
resolution_precision = Some("heuristic".to_owned());
fallback_reason = precise_matches
.iter()
.find_map(|implementation_match| implementation_match.fallback_reason.clone())
.or_else(|| Some("precise_absent".to_owned()));
for implementation_match in &mut precise_matches {
if implementation_match.fallback_reason.is_none() {
implementation_match.fallback_reason = fallback_reason.clone();
}
}
} else {
resolution_precision =
Some(Self::precise_resolution_precision(precise_coverage).to_owned());
}
let total_implementations = precise_matches.len();
if precise_matches.len() > limit {
precise_matches.truncate(limit);
}
if include_follow_up_structural {
Self::populate_implementation_match_follow_up_structural(
&target.root,
&mut precise_matches,
);
}
match_count = precise_matches.len();
let metadata = json!({
"precision": resolution_precision.clone().unwrap_or_else(|| "heuristic".to_owned()),
"heuristic": resolution_precision.as_deref() == Some("heuristic"),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"fallback_reason": fallback_reason,
"precise_absence_reason": fallback_reason.as_ref().map(|_| {
Self::precise_absence_reason(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
)
}),
"scoped_repository_ids": scoped_repository_ids.clone(),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"implementation_count",
precise_matches.len(),
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
Ok(Json(FindImplementationsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::Implementation,
precise_matches.len(),
Some(total_implementations),
Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
precise_matches.len() < total_implementations,
None,
),
matches: precise_matches,
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
target_selection: target_selection.clone(),
metadata,
note,
recovery: RecoveryFields::default(),
}))
})()
});
execution.await?.map(|Json(mut response)| {
self.paginate_navigation_rows(
&mut response.matches,
&mut response.completeness,
response.mode,
page_limit,
resume_offset,
continuation_binding.clone(),
);
Json(self.present_find_implementations_response(response, params.response_mode))
})
}
pub(in crate::mcp::server) async fn incoming_calls_impl(
&self,
mut params: IncomingCallsParams,
) -> Result<Json<IncomingCallsResponse>, ErrorData> {
Self::validate_navigation_target_inputs(
params.target.as_ref(),
params.symbol.as_deref(),
params.path.as_deref(),
params.line,
params.column,
)?;
params.repository_id = self.navigation_target_repository_hint(
params.target.as_ref(),
params.repository_id.as_deref(),
)?;
self.incoming_calls_for_resolved_target_impl(params, None)
.await
}
pub(in crate::mcp::server) async fn incoming_calls_for_resolved_target_impl(
&self,
params: IncomingCallsParams,
resolved_target: Option<ResolvedNavigationTarget>,
) -> Result<Json<IncomingCallsResponse>, ErrorData> {
let page_limit = self.navigation_page_limit(params.limit)?;
let (resume_offset, continuation_binding) = self.navigation_continuation_context(
"incoming_calls",
¶ms,
params.repository_id.clone(),
ResultUnit::IncomingCall,
)?;
let execution_context =
self.read_only_tool_execution_context("incoming_calls", params.repository_id.clone());
let execution_context_for_blocking = execution_context.clone();
let resource_budgets = self.find_references_resource_budgets();
let params_for_blocking = params.clone();
let resolved_target_for_blocking = resolved_target.clone();
let server = self.clone();
let execution = self.run_read_only_tool_blocking(&execution_context, move || {
let mut scoped_repository_ids: Vec<String> = Vec::new();
let mut selected_symbol_id: Option<String> = None;
let mut selected_precise_symbol: Option<String> = None;
let mut resolution_precision: Option<String> = None;
let mut resolution_source: Option<String> = None;
let mut target_selection_candidate_count = 0usize;
let mut target_selection_same_rank_count = 0usize;
let mut precise_artifacts_ingested = 0usize;
let mut precise_artifacts_failed = 0usize;
(|| -> Result<Json<IncomingCallsResponse>, ErrorData> {
let limit = usize::MAX;
let include_follow_up_structural =
params_for_blocking.include_follow_up_structural == Some(true);
let freshness_basis = server
.scoped_read_only_tool_execution_context(
execution_context_for_blocking.tool_name,
execution_context_for_blocking.repository_hint.clone(),
RepositoryResponseCacheFreshnessMode::ManifestOnly,
)?
.cache_freshness
.basis;
let attach_freshness =
|metadata| Self::metadata_with_freshness_basis(metadata, &freshness_basis);
let corpora = server.collect_repository_symbol_corpora(
params_for_blocking.repository_id.as_deref(),
)?;
scoped_repository_ids = corpora
.iter()
.map(|corpus| corpus.repository_id.clone())
.collect::<Vec<_>>();
let resolved_target = match resolved_target_for_blocking.clone() {
Some(resolved_target) => resolved_target,
None => server.resolve_navigation_request(
&corpora,
params_for_blocking.target.as_ref(),
params_for_blocking.symbol.as_deref(),
params_for_blocking.path.as_deref(),
params_for_blocking.line,
params_for_blocking.column,
params_for_blocking.repository_id.as_deref(),
)?,
};
resolution_source = Some(resolved_target.resolution_source.to_owned());
let symbol_query = resolved_target.symbol_query;
let target_selection =
Some(Self::navigation_target_selection_summary_for_selection(
&corpora,
&symbol_query,
&resolved_target.selection,
resolved_target.resolution_source,
));
let target_resolution = match resolved_target.selection {
NavigationTargetSelection::Resolved(target_resolution) => target_resolution,
NavigationTargetSelection::DisambiguationRequired(_) => {
let availability = NavigationAvailability {
status: "unavailable".to_owned(),
reason: Some("disambiguation_required".to_owned()),
precise_required_for_complete_results: false,
};
let metadata = json!({
"precision": "unavailable",
"heuristic": false,
"disambiguation_required": true,
"availability": availability.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_summary_value(
target_selection
.as_ref()
.expect("target selection summary should be present"),
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(IncomingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::IncomingCall,
0,
Some(0),
NavigationMode::UnavailableNoPrecise,
false,
None,
),
matches: Vec::new(),
result_handle: None,
mode: NavigationMode::UnavailableNoPrecise,
availability: Some(availability),
target_selection,
metadata,
note,
recovery: RecoveryFields::default(),
}));
}
};
target_selection_candidate_count = target_resolution.candidate_count;
target_selection_same_rank_count = target_resolution.selected_rank_candidate_count;
let target = target_resolution.candidate;
selected_symbol_id = Some(target.symbol.stable_id.clone());
let target_corpus = target_resolution.corpus;
let cached_precise_graph =
server.precise_graph_for_corpus(target_corpus.as_ref(), resource_budgets)?;
let precise_coverage = cached_precise_graph.coverage_mode;
let graph = cached_precise_graph.graph;
precise_artifacts_ingested = cached_precise_graph.ingest_stats.artifacts_ingested;
precise_artifacts_failed = cached_precise_graph.ingest_stats.artifacts_failed;
let mut precise_matches = Vec::new();
let precise_targets = Self::matching_precise_symbols_for_resolved_target(
graph.as_ref(),
&target_corpus.repository_id,
&target.root,
&symbol_query,
&target.symbol,
);
for precise_target in &precise_targets {
let matches = Self::precise_incoming_matches_from_relationships(
graph.as_ref(),
&target_corpus.repository_id,
&target.root,
&target.symbol.name,
precise_coverage,
precise_target,
);
if !matches.is_empty() {
selected_precise_symbol = Some(precise_target.symbol.clone());
precise_matches = matches;
break;
}
}
if precise_matches.is_empty() {
for precise_target in &precise_targets {
let matches = Self::precise_incoming_matches_from_occurrences(
graph.as_ref(),
target_corpus.as_ref(),
&target.root,
&target.symbol.name,
precise_coverage,
precise_target,
&target.symbol.stable_id,
);
if !matches.is_empty() {
selected_precise_symbol = Some(precise_target.symbol.clone());
precise_matches = matches;
break;
}
}
}
if !precise_matches.is_empty() {
let total_incoming_calls = precise_matches.len();
if precise_matches.len() > limit {
precise_matches.truncate(limit);
}
if include_follow_up_structural {
Self::populate_call_hierarchy_match_follow_up_structural(
&target.root,
&mut precise_matches,
);
}
let precision = Self::precise_resolution_precision(precise_coverage).to_owned();
resolution_precision = Some(precision.clone());
let availability = Self::call_hierarchy_availability(
precise_coverage,
&cached_precise_graph.ingest_stats,
precise_matches.len(),
0,
);
let metadata = json!({
"precision": precision,
"heuristic": false,
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"incoming_count",
precise_matches.len(),
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(IncomingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::IncomingCall,
precise_matches.len(),
Some(total_incoming_calls),
Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
precise_matches.len() < total_incoming_calls,
None,
),
matches: precise_matches,
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
recovery: RecoveryFields::default(),
}));
}
if precise_coverage == PreciseCoverageMode::Full {
let precision = Self::precise_resolution_precision(precise_coverage).to_owned();
resolution_precision = Some(precision.clone());
let availability = Self::call_hierarchy_availability(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
0,
);
let metadata = json!({
"precision": precision,
"heuristic": false,
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"incoming_count",
0,
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(IncomingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::IncomingCall,
0,
Some(0),
Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
false,
None,
),
matches: Vec::new(),
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
recovery: RecoveryFields::default(),
}));
}
let (target_container, target_signature) = Self::symbol_context_for_stable_id(
target_corpus.as_ref(),
&target.symbol.stable_id,
);
let mut matches = graph
.incoming_adjacency(&target.symbol.stable_id)
.into_iter()
.filter(|adjacent| Self::is_heuristic_call_relation(adjacent.relation))
.map(|adjacent| {
let source_stable_symbol_id = adjacent.symbol.symbol_id.clone();
let column = target_corpus
.symbol_index_by_stable_id
.get(&source_stable_symbol_id)
.map(|index| target_corpus.symbols[*index].span.start_column)
.unwrap_or(1);
CallHierarchyMatch {
match_id: None,
target_ref: None,
source_stable_symbol_id: Some(source_stable_symbol_id),
target_stable_symbol_id: Some(target.symbol.stable_id.clone()),
source_symbol: adjacent.symbol.display_name,
target_symbol: target.symbol.name.clone(),
repository_id: target_corpus.repository_id.clone(),
path: Self::canonicalize_navigation_path(
&target.root,
&adjacent.symbol.path,
),
line: adjacent.symbol.line,
column,
relation: adjacent.relation.as_str().to_owned(),
source_container: None,
target_container: target_container.clone(),
source_signature: None,
target_signature: target_signature.clone(),
precision: Some("heuristic".to_owned()),
call_path: None,
call_line: None,
call_column: None,
call_end_line: None,
call_end_column: None,
follow_up_structural: Vec::new(),
}
})
.collect::<Vec<_>>();
Self::sort_call_hierarchy_matches(&mut matches);
let total_incoming_calls = matches.len();
if matches.len() > limit {
matches.truncate(limit);
}
if include_follow_up_structural {
Self::populate_call_hierarchy_match_follow_up_structural(
&target.root,
&mut matches,
);
}
resolution_precision = Some("heuristic".to_owned());
let fallback_reason = "precise_absent";
let availability = NavigationAvailability {
status: "heuristic".to_owned(),
reason: Some(
Self::precise_absence_reason(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
)
.to_owned(),
),
precise_required_for_complete_results: true,
};
let metadata = json!({
"precision": "heuristic",
"heuristic": true,
"fallback_reason": fallback_reason,
"precise_absence_reason": Self::precise_absence_reason(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
),
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"incoming_count",
0,
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
Ok(Json(IncomingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::IncomingCall,
matches.len(),
Some(total_incoming_calls),
Self::navigation_mode_from_precision_label(resolution_precision.as_deref()),
matches.len() < total_incoming_calls,
None,
),
matches,
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
recovery: RecoveryFields::default(),
}))
})()
});
execution.await?.map(|Json(mut response)| {
self.paginate_navigation_rows(
&mut response.matches,
&mut response.completeness,
response.mode,
page_limit,
resume_offset,
continuation_binding.clone(),
);
Json(self.present_incoming_calls_response(response, params.response_mode))
})
}
pub(in crate::mcp::server) async fn outgoing_calls_impl(
&self,
mut params: OutgoingCallsParams,
) -> Result<Json<OutgoingCallsResponse>, ErrorData> {
Self::validate_navigation_target_inputs(
params.target.as_ref(),
params.symbol.as_deref(),
params.path.as_deref(),
params.line,
params.column,
)?;
params.repository_id = self.navigation_target_repository_hint(
params.target.as_ref(),
params.repository_id.as_deref(),
)?;
let page_limit = self.navigation_page_limit(params.limit)?;
let (resume_offset, continuation_binding) = self.navigation_continuation_context(
"outgoing_calls",
¶ms,
params.repository_id.clone(),
ResultUnit::OutgoingCall,
)?;
let execution_context =
self.read_only_tool_execution_context("outgoing_calls", params.repository_id.clone());
let execution_context_for_blocking = execution_context.clone();
let resource_budgets = self.find_references_resource_budgets();
let params_for_blocking = params.clone();
let server = self.clone();
let execution = self.run_read_only_tool_blocking(&execution_context, move || {
let mut scoped_repository_ids: Vec<String> = Vec::new();
let mut selected_symbol_id: Option<String> = None;
let mut selected_precise_symbol: Option<String> = None;
let mut resolution_precision: Option<String> = None;
let mut resolution_source: Option<String> = None;
let mut target_selection_candidate_count = 0usize;
let mut target_selection_same_rank_count = 0usize;
let mut precise_artifacts_ingested = 0usize;
let mut precise_artifacts_failed = 0usize;
(|| -> Result<Json<OutgoingCallsResponse>, ErrorData> {
let limit = usize::MAX;
let include_follow_up_structural =
params_for_blocking.include_follow_up_structural == Some(true);
let freshness_basis = server
.scoped_read_only_tool_execution_context(
execution_context_for_blocking.tool_name,
execution_context_for_blocking.repository_hint.clone(),
RepositoryResponseCacheFreshnessMode::ManifestOnly,
)?
.cache_freshness
.basis;
let attach_freshness =
|metadata| Self::metadata_with_freshness_basis(metadata, &freshness_basis);
let corpora = server.collect_repository_symbol_corpora(
params_for_blocking.repository_id.as_deref(),
)?;
scoped_repository_ids = corpora
.iter()
.map(|corpus| corpus.repository_id.clone())
.collect::<Vec<_>>();
let resolved_target = server.resolve_navigation_request(
&corpora,
params_for_blocking.target.as_ref(),
params_for_blocking.symbol.as_deref(),
params_for_blocking.path.as_deref(),
params_for_blocking.line,
params_for_blocking.column,
params_for_blocking.repository_id.as_deref(),
)?;
resolution_source = Some(resolved_target.resolution_source.to_owned());
let symbol_query = resolved_target.symbol_query;
let target_selection =
Some(Self::navigation_target_selection_summary_for_selection(
&corpora,
&symbol_query,
&resolved_target.selection,
resolved_target.resolution_source,
));
let target_resolution = match resolved_target.selection {
NavigationTargetSelection::Resolved(target_resolution) => target_resolution,
NavigationTargetSelection::DisambiguationRequired(_) => {
let availability = NavigationAvailability {
status: "unavailable".to_owned(),
reason: Some("disambiguation_required".to_owned()),
precise_required_for_complete_results: false,
};
let metadata = json!({
"precision": "unavailable",
"heuristic": false,
"disambiguation_required": true,
"availability": availability.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_summary_value(
target_selection
.as_ref()
.expect("target selection summary should be present"),
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(OutgoingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::OutgoingCall,
0,
Some(0),
NavigationMode::UnavailableNoPrecise,
false,
None,
),
matches: Vec::new(),
result_handle: None,
mode: NavigationMode::UnavailableNoPrecise,
availability: Some(availability),
target_selection,
metadata,
note,
trust: NavigationEdgeTrust::Provisional,
trust_note: crate::mcp::types::OUTGOING_CALLS_TRUST_NOTE.to_owned(),
recovery: RecoveryFields::default(),
}));
}
};
target_selection_candidate_count = target_resolution.candidate_count;
target_selection_same_rank_count = target_resolution.selected_rank_candidate_count;
let target = target_resolution.candidate;
selected_symbol_id = Some(target.symbol.stable_id.clone());
let target_corpus = target_resolution.corpus;
let cached_precise_graph =
server.precise_graph_for_corpus(target_corpus.as_ref(), resource_budgets)?;
let precise_coverage = cached_precise_graph.coverage_mode;
let graph = cached_precise_graph.graph;
precise_artifacts_ingested = cached_precise_graph.ingest_stats.artifacts_ingested;
precise_artifacts_failed = cached_precise_graph.ingest_stats.artifacts_failed;
let mut precise_matches = Vec::new();
let mut precise_occurrence_cache = std::collections::BTreeMap::<
String,
Vec<crate::graph::PreciseOccurrenceRecord>,
>::new();
let mut precise_source_cache =
std::collections::BTreeMap::<String, Option<String>>::new();
let precise_targets = Self::matching_precise_symbols_for_resolved_target(
graph.as_ref(),
&target_corpus.repository_id,
&target.root,
&symbol_query,
&target.symbol,
);
for precise_target in &precise_targets {
let matches = Self::precise_outgoing_matches_from_occurrences(
graph.as_ref(),
target_corpus.as_ref(),
&target.root,
&target.symbol.name,
precise_coverage,
precise_target,
&target.symbol.stable_id,
&mut precise_occurrence_cache,
&mut precise_source_cache,
);
if !matches.is_empty() {
selected_precise_symbol = Some(precise_target.symbol.clone());
precise_matches = matches;
break;
}
}
if !precise_matches.is_empty() {
let total_outgoing_calls = precise_matches.len();
if precise_matches.len() > limit {
precise_matches.truncate(limit);
}
if include_follow_up_structural {
Self::populate_call_hierarchy_match_follow_up_structural(
&target.root,
&mut precise_matches,
);
}
let precision = Self::precise_resolution_precision(precise_coverage).to_owned();
resolution_precision = Some(precision.clone());
let availability = Self::call_hierarchy_availability(
precise_coverage,
&cached_precise_graph.ingest_stats,
precise_matches.len(),
0,
);
let metadata = json!({
"precision": precision,
"heuristic": false,
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"scoped_repository_ids": scoped_repository_ids.clone(),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"outgoing_count",
precise_matches.len(),
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(OutgoingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::OutgoingCall,
precise_matches.len(),
Some(total_outgoing_calls),
Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
precise_matches.len() < total_outgoing_calls,
None,
),
matches: precise_matches,
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
trust: NavigationEdgeTrust::Provisional,
trust_note: crate::mcp::types::OUTGOING_CALLS_TRUST_NOTE.to_owned(),
recovery: RecoveryFields::default(),
}));
}
if precise_coverage == PreciseCoverageMode::Full {
let precision = Self::precise_resolution_precision(precise_coverage).to_owned();
resolution_precision = Some(precision.clone());
let availability = Self::call_hierarchy_availability(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
0,
);
let metadata = json!({
"precision": precision,
"heuristic": false,
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"scoped_repository_ids": scoped_repository_ids.clone(),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"outgoing_count",
0,
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
return Ok(Json(OutgoingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::OutgoingCall,
0,
Some(0),
Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
false,
None,
),
matches: Vec::new(),
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
trust: NavigationEdgeTrust::Provisional,
trust_note: crate::mcp::types::OUTGOING_CALLS_TRUST_NOTE.to_owned(),
recovery: RecoveryFields::default(),
}));
}
let (source_container, source_signature) = Self::symbol_context_for_stable_id(
target_corpus.as_ref(),
&target.symbol.stable_id,
);
let mut source_cache: std::collections::BTreeMap<String, Option<String>> =
std::collections::BTreeMap::new();
let mut call_target_cache: std::collections::BTreeMap<
String,
Option<std::collections::BTreeSet<String>>,
> = std::collections::BTreeMap::new();
let mut matches = graph
.outgoing_adjacency(&target.symbol.stable_id)
.into_iter()
.filter(|adjacent| {
Self::is_heuristic_call_relation(adjacent.relation)
&& Self::is_heuristic_callable_kind(&adjacent.symbol.kind)
&& Self::heuristic_symbol_body_has_call_like_reference(
&target.root,
&target.symbol,
adjacent.symbol.display_name.as_str(),
&mut source_cache,
&mut call_target_cache,
)
})
.map(|adjacent| {
let target_stable_symbol_id = adjacent.symbol.symbol_id.clone();
let column = target_corpus
.symbol_index_by_stable_id
.get(&target_stable_symbol_id)
.map(|index| target_corpus.symbols[*index].span.start_column)
.unwrap_or(1);
CallHierarchyMatch {
match_id: None,
target_ref: None,
source_stable_symbol_id: Some(target.symbol.stable_id.clone()),
target_stable_symbol_id: Some(target_stable_symbol_id),
source_symbol: target.symbol.name.clone(),
target_symbol: adjacent.symbol.display_name,
repository_id: target_corpus.repository_id.clone(),
path: Self::canonicalize_navigation_path(
&target.root,
&adjacent.symbol.path,
),
line: adjacent.symbol.line,
column,
relation: adjacent.relation.as_str().to_owned(),
source_container: source_container.clone(),
target_container: None,
source_signature: source_signature.clone(),
target_signature: None,
precision: Some("heuristic".to_owned()),
call_path: None,
call_line: None,
call_column: None,
call_end_line: None,
call_end_column: None,
follow_up_structural: Vec::new(),
}
})
.collect::<Vec<_>>();
Self::sort_call_hierarchy_matches(&mut matches);
let total_outgoing_calls = matches.len();
if matches.len() > limit {
matches.truncate(limit);
}
if include_follow_up_structural {
Self::populate_call_hierarchy_match_follow_up_structural(
&target.root,
&mut matches,
);
}
resolution_precision = Some("heuristic".to_owned());
let fallback_reason = "precise_absent";
let availability = NavigationAvailability {
status: "heuristic".to_owned(),
reason: Some(
Self::precise_absence_reason(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
)
.to_owned(),
),
precise_required_for_complete_results: true,
};
let metadata = json!({
"precision": "heuristic",
"heuristic": true,
"fallback_reason": fallback_reason,
"precise_absence_reason": Self::precise_absence_reason(
precise_coverage,
&cached_precise_graph.ingest_stats,
0,
),
"availability": availability.clone(),
"target_symbol_id": target.symbol.stable_id.clone(),
"target_precise_symbol": selected_precise_symbol.clone(),
"resolution_source": resolution_source.clone(),
"target_selection": Self::navigation_target_selection_note(
&symbol_query,
&target,
target_selection_candidate_count,
target_selection_same_rank_count,
),
"scoped_repository_ids": scoped_repository_ids.clone(),
"precise_artifacts_ingested": precise_artifacts_ingested,
"precise_artifacts_failed": precise_artifacts_failed,
"precise": Self::precise_note_with_count(
precise_coverage,
&cached_precise_graph.ingest_stats,
"outgoing_count",
0,
),
});
let (metadata, note) = Self::metadata_note_pair(attach_freshness(metadata));
Ok(Json(OutgoingCallsResponse {
completeness: FriggMcpServer::navigation_completeness(
ResultUnit::OutgoingCall,
matches.len(),
Some(total_outgoing_calls),
Self::navigation_mode_from_precision_label(resolution_precision.as_deref()),
matches.len() < total_outgoing_calls,
None,
),
matches,
result_handle: None,
mode: Self::navigation_mode_from_precision_label(
resolution_precision.as_deref(),
),
availability: Some(availability),
target_selection: target_selection.clone(),
metadata,
note,
trust: NavigationEdgeTrust::Provisional,
trust_note: crate::mcp::types::OUTGOING_CALLS_TRUST_NOTE.to_owned(),
recovery: RecoveryFields::default(),
}))
})()
});
execution.await?.map(|Json(mut response)| {
self.paginate_navigation_rows(
&mut response.matches,
&mut response.completeness,
response.mode,
page_limit,
resume_offset,
continuation_binding.clone(),
);
Json(self.present_outgoing_calls_response(response, params.response_mode))
})
}
fn heuristic_symbol_body_has_call_like_reference(
root: &Path,
source_symbol: &SymbolDefinition,
target_name: &str,
source_cache: &mut std::collections::BTreeMap<String, Option<String>>,
call_target_cache: &mut std::collections::BTreeMap<
String,
Option<std::collections::BTreeSet<String>>,
>,
) -> bool {
if target_name.trim().is_empty() {
return false;
}
let call_targets = call_target_cache
.entry(source_symbol.stable_id.clone())
.or_insert_with(|| {
Self::heuristic_symbol_body_call_targets(root, source_symbol, source_cache)
});
call_targets
.as_ref()
.is_some_and(|targets| targets.contains(target_name))
}
fn heuristic_symbol_body_call_targets(
root: &Path,
source_symbol: &SymbolDefinition,
source_cache: &mut std::collections::BTreeMap<String, Option<String>>,
) -> Option<std::collections::BTreeSet<String>> {
let relative_path = Self::relative_display_path(root, &source_symbol.path);
let source = source_cache
.entry(relative_path)
.or_insert_with(|| {
if !Self::navigation_path_within_root(root, &source_symbol.path) {
return None;
}
fs::read_to_string(&source_symbol.path).ok()
})
.as_deref()?;
let start = source_symbol.span.start_byte.min(source.len());
let end = source_symbol.span.end_byte.min(source.len());
let body = source.get(start..end)?;
let bytes = body.as_bytes();
let mut targets = std::collections::BTreeSet::new();
let mut index = 0usize;
while index < bytes.len() {
let byte = bytes[index];
if !(byte.is_ascii_alphabetic() || byte == b'_') {
index = index.saturating_add(1);
continue;
}
let start_index = index;
index = index.saturating_add(1);
while index < bytes.len()
&& (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_')
{
index = index.saturating_add(1);
}
let Some(name) = body.get(start_index..index) else {
continue;
};
let suffix = body.get(index..).unwrap_or_default();
let is_call = match source_symbol.language {
SymbolLanguage::Rust => rust_source_suffix_looks_like_call(suffix),
_ => suffix.trim_start().starts_with('('),
};
if is_call {
targets.insert(name.to_owned());
}
}
Some(targets)
}
}