use camino::Utf8PathBuf;
use cordance_core::evidence::{EvidenceEntry, EvidenceMap, EvidenceSource};
use cordance_emit::TargetEmitter;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::mcp::error::{McpToolError, McpToolResult};
#[derive(Clone, Debug, Deserialize, JsonSchema)]
pub struct EvidenceLookupParams {
#[serde(default)]
pub target: Option<String>,
pub rule_id: String,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct EvidenceLookupOutput {
pub schema: String,
pub matches: Vec<EvidenceEntrySummary>,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct EvidenceEntrySummary {
pub rule_id: String,
pub text: String,
pub sources: Vec<EvidenceSourceSummary>,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct EvidenceSourceSummary {
#[schemars(with = "String")]
pub path: Utf8PathBuf,
pub kind: String,
pub line_range: Option<(u32, u32)>,
}
pub fn lookup(
target: &Utf8PathBuf,
cfg: &Config,
rule_id: &str,
) -> McpToolResult<EvidenceLookupOutput> {
if rule_id.is_empty() {
return Err(McpToolError::InvalidArgument(
"rule_id must not be empty".into(),
));
}
let mut pack = super::pack::build_pack(target, cfg)?;
if pack.advise.findings.is_empty() {
if let Ok(report) = cordance_advise::run_all(&pack) {
pack.advise = report;
}
}
let emitter = cordance_emit::evidence_map::EvidenceMapEmitter;
let rendered = emitter
.render(&pack)
.map_err(|e| McpToolError::internal_redacted("evidence map render", e))?;
let (_, bytes) = rendered
.first()
.ok_or_else(|| McpToolError::Internal("evidence map emitter produced no output".into()))?
.clone();
let map: EvidenceMap = serde_json::from_slice(&bytes)
.map_err(|e| McpToolError::internal_redacted("evidence map parse", e))?;
let matches: Vec<EvidenceEntrySummary> = map
.rules
.iter()
.filter(|e| e.rule_id.contains(rule_id))
.map(summarise_entry)
.collect();
Ok(EvidenceLookupOutput {
schema: "cordance-evidence-lookup.v1".to_string(),
matches,
})
}
fn summarise_entry(e: &EvidenceEntry) -> EvidenceEntrySummary {
EvidenceEntrySummary {
rule_id: e.rule_id.clone(),
text: e.text.clone(),
sources: e.sources.iter().map(summarise_source).collect(),
}
}
fn summarise_source(s: &EvidenceSource) -> EvidenceSourceSummary {
EvidenceSourceSummary {
path: s.path.clone(),
kind: s.kind.clone(),
line_range: s.line_range,
}
}