use camino::Utf8PathBuf;
use cordance_core::advise::{AdviseFinding, AdviseReport, Severity};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::mcp::error::{McpToolError, McpToolResult};
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
pub struct AdviseFindingsParams {
#[serde(default)]
pub target: Option<String>,
}
#[derive(Clone, Debug, Deserialize, JsonSchema)]
pub struct AdviseByRuleParams {
#[serde(default)]
pub target: Option<String>,
pub rule_id: String,
}
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
pub struct AdviseRunParams {
#[serde(default)]
pub target: Option<String>,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct AdviseReportOutput {
pub schema: String,
pub generated_at: String,
pub findings: Vec<AdviseFindingSummary>,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct AdviseFindingSummary {
pub id: String,
pub severity: String,
pub summary: String,
#[schemars(with = "String")]
pub doctrine_anchor: Utf8PathBuf,
#[schemars(with = "Vec<String>")]
pub project_paths: Vec<Utf8PathBuf>,
pub remediation: String,
}
pub fn findings(target: &Utf8PathBuf, cfg: &Config) -> McpToolResult<AdviseReportOutput> {
let pack = super::pack::build_pack(target, cfg)?;
let report = cordance_advise::run_all(&pack)
.map_err(|e| McpToolError::Internal(format!("advise failed: {e}")))?;
Ok(to_summary(&report))
}
pub fn by_rule(
target: &Utf8PathBuf,
cfg: &Config,
rule_id: &str,
) -> McpToolResult<AdviseReportOutput> {
if rule_id.is_empty() {
return Err(McpToolError::InvalidArgument(
"rule_id must not be empty".into(),
));
}
let report = run_report(target, cfg)?;
let filtered: Vec<AdviseFinding> = report
.findings
.iter()
.filter(|f| f.id.contains(rule_id))
.cloned()
.collect();
Ok(to_summary(&AdviseReport {
schema: report.schema,
findings: filtered,
}))
}
pub fn run(target: &Utf8PathBuf, cfg: &Config) -> McpToolResult<AdviseReportOutput> {
let report = run_report(target, cfg)?;
Ok(to_summary(&report))
}
fn run_report(target: &Utf8PathBuf, cfg: &Config) -> McpToolResult<AdviseReport> {
let pack = super::pack::build_pack(target, cfg)?;
cordance_advise::run_all(&pack)
.map_err(|e| McpToolError::Internal(format!("advise failed: {e}")))
}
fn to_summary(report: &AdviseReport) -> AdviseReportOutput {
AdviseReportOutput {
schema: report.schema.clone(),
generated_at: chrono::Utc::now().to_rfc3339(),
findings: report.findings.iter().map(summarise_finding).collect(),
}
}
fn summarise_finding(f: &AdviseFinding) -> AdviseFindingSummary {
AdviseFindingSummary {
id: f.id.clone(),
severity: severity_label(f.severity).to_string(),
summary: f.summary.clone(),
doctrine_anchor: f.doctrine_anchor.clone(),
project_paths: f.project_paths.clone(),
remediation: f.remediation.clone(),
}
}
const fn severity_label(s: Severity) -> &'static str {
match s {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
}
}