use crate::error::ChangeSetError;
use crate::output_adapters::{
default_summary, matches_file_filters, DetailLevel, OutputAdapter, RenderContext,
};
use crate::pr_package::{Artifact, ChangeType};
#[derive(Default)]
pub struct MarkdownAdapter {}
impl MarkdownAdapter {
pub fn new() -> Self {
Self {}
}
fn change_icon(&self, change_type: &ChangeType) -> &str {
match change_type {
ChangeType::Add => "➕",
ChangeType::Modify => "✏️",
ChangeType::Delete => "🗑️",
ChangeType::Rename => "📝",
}
}
}
impl OutputAdapter for MarkdownAdapter {
fn render(&self, ctx: &RenderContext) -> Result<String, ChangeSetError> {
let pkg = ctx.package;
let mut output = String::new();
output.push_str(&format!("# Draft: {}\n\n", pkg.package_id));
output.push_str(&format!("**Status**: {}\n\n", pkg.status));
output.push_str(&format!("**Goal**: {}\n\n", pkg.goal.title));
output.push_str(&format!(
"**Created**: {}\n\n",
pkg.created_at.format("%Y-%m-%d %H:%M:%S")
));
output.push_str("## Summary\n\n");
output.push_str(&format!(
"**What changed**: {}\n\n",
pkg.summary.what_changed
));
output.push_str(&format!("**Why**: {}\n\n", pkg.summary.why));
output.push_str(&format!("**Impact**: {}\n\n", pkg.summary.impact));
output.push_str(&format!(
"## Changes ({} artifacts)\n\n",
pkg.changes.artifacts.len()
));
let artifacts: Vec<&Artifact> = pkg
.changes
.artifacts
.iter()
.filter(|a| matches_file_filters(&a.resource_uri, &ctx.file_filters))
.collect();
for artifact in artifacts {
let icon = self.change_icon(&artifact.change_type);
match ctx.detail_level {
DetailLevel::Top => {
let summary = artifact
.explanation_tiers
.as_ref()
.map(|t| t.summary.as_str())
.or(artifact.rationale.as_deref())
.unwrap_or_else(|| {
default_summary(&artifact.resource_uri, &artifact.change_type)
});
output.push_str(&format!(
"- {} **{}** — {}\n",
icon, artifact.resource_uri, summary
));
}
DetailLevel::Medium | DetailLevel::Full => {
output.push_str(&format!("\n### {} {}\n\n", icon, artifact.resource_uri));
if let Some(tiers) = &artifact.explanation_tiers {
output.push_str(&format!("**Summary**: {}\n\n", tiers.summary));
output.push_str(&format!("{}\n\n", tiers.explanation));
if !tiers.tags.is_empty() {
output.push_str(&format!("**Tags**: {}\n\n", tiers.tags.join(", ")));
}
if !tiers.related_artifacts.is_empty() {
output.push_str("**Related artifacts**:\n");
for related in &tiers.related_artifacts {
output.push_str(&format!("- {}\n", related));
}
output.push('\n');
}
} else if let Some(rationale) = &artifact.rationale {
output.push_str(&format!("**Rationale**: {}\n\n", rationale));
}
if ctx.detail_level == DetailLevel::Full {
if let Some(provider) = ctx.diff_provider {
match provider.get_diff(&artifact.diff_ref) {
Ok(diff) => {
output.push_str(
"<details>\n<summary>View diff</summary>\n\n```diff\n",
);
output.push_str(&diff);
output.push_str("\n```\n</details>\n\n");
}
Err(_) => {
output.push_str(&format!("*Diff: {}*\n\n", artifact.diff_ref));
}
}
}
}
}
}
}
output.push_str("\n---\n\n");
output.push_str(&format!(
"🤖 Generated by Trusted Autonomy v{}\n",
pkg.package_version
));
Ok(output)
}
fn name(&self) -> &str {
"markdown"
}
}