use crate::server::helpers::{pathfinder_to_error_data, serialize_metadata};
use crate::server::types::{GetSemanticPathResult, LocateParams};
use crate::server::PathfinderServer;
use rmcp::model::{CallToolResult, ErrorData};
impl PathfinderServer {
pub(crate) async fn get_semantic_path_impl(
&self,
params: LocateParams,
) -> Result<CallToolResult, ErrorData> {
let start = std::time::Instant::now();
let file = params
.file
.as_ref()
.ok_or_else(|| rmcp::model::ErrorData::invalid_params("file is required", None))?;
let line = params
.line
.ok_or_else(|| rmcp::model::ErrorData::invalid_params("line is required", None))?;
tracing::info!(
tool = "get_semantic_path",
file = %file,
line = line,
"get_semantic_path: start"
);
if let Err(e) = self.sandbox.check(std::path::Path::new(file)) {
let duration_ms = start.elapsed().as_millis();
tracing::warn!(
tool = "get_semantic_path",
error_code = e.error_code(),
duration_ms,
"sandbox check failed"
);
return Err(pathfinder_to_error_data(&e));
}
let abs_path = self.workspace_root.path().join(file);
if !abs_path.exists() {
let err = pathfinder_common::error::PathfinderError::FileNotFound {
path: abs_path.clone(),
};
tracing::warn!(
tool = "get_semantic_path",
path = %abs_path.display(),
"file not found"
);
return Err(pathfinder_to_error_data(&err));
}
let line_idx = line as usize;
let symbol_result = self
.surgeon
.enclosing_symbol(
self.workspace_root.path(),
std::path::Path::new(file),
line_idx,
)
.await;
let symbol = match symbol_result {
Ok(sym) => sym,
Err(e) => {
let duration_ms = start.elapsed().as_millis();
tracing::warn!(
tool = "get_semantic_path",
file = %file,
line = line_idx,
error = %e,
duration_ms,
"enclosing_symbol failed"
);
return Err(crate::server::helpers::treesitter_error_to_error_data(e));
}
};
let duration_ms = start.elapsed().as_millis();
let semantic_path = symbol.as_deref().map(|sym| format!("{file}::{sym}"));
let result = GetSemanticPathResult {
semantic_path: semantic_path.clone(),
symbol: symbol.clone(),
file: file.clone(),
line,
};
let text = match &semantic_path {
Some(sp) => format!("{sp}\n\n[resolved in {duration_ms}ms]"),
None => format!(
"Line {line} in '{file}' is not inside a named symbol.\n\n\
The line may be a module-level attribute, blank line, or top-level import. \
Use `read(filepath=\"{file}\", detail_level=\"symbols\")` to see \
the available symbols in this file.\n\n\
[resolved in {duration_ms}ms]",
),
};
tracing::info!(
tool = "get_semantic_path",
file = %file,
line = line_idx,
semantic_path = ?semantic_path,
duration_ms,
"get_semantic_path: complete"
);
let mut call_result = CallToolResult::success(vec![rmcp::model::Content::text(text)]);
call_result.structured_content = serialize_metadata(&result);
Ok(call_result)
}
}
#[cfg(test)]
#[path = "semantic_path_test.rs"]
mod tests;