ta_changeset/output_adapters/
markdown.rs1use crate::error::ChangeSetError;
4use crate::output_adapters::{
5 default_summary, matches_file_filters, DetailLevel, OutputAdapter, RenderContext,
6};
7use crate::pr_package::{Artifact, ChangeType};
8
9#[derive(Default)]
10pub struct MarkdownAdapter {}
11
12impl MarkdownAdapter {
13 pub fn new() -> Self {
14 Self {}
15 }
16
17 fn change_icon(&self, change_type: &ChangeType) -> &str {
18 match change_type {
19 ChangeType::Add => "➕",
20 ChangeType::Modify => "✏️",
21 ChangeType::Delete => "🗑️",
22 ChangeType::Rename => "📝",
23 }
24 }
25}
26
27impl OutputAdapter for MarkdownAdapter {
28 fn render(&self, ctx: &RenderContext) -> Result<String, ChangeSetError> {
29 let pkg = ctx.package;
30 let mut output = String::new();
31
32 output.push_str(&format!("# Draft: {}\n\n", pkg.package_id));
34 output.push_str(&format!("**Status**: {}\n\n", pkg.status));
35 output.push_str(&format!("**Goal**: {}\n\n", pkg.goal.title));
36 output.push_str(&format!(
37 "**Created**: {}\n\n",
38 pkg.created_at.format("%Y-%m-%d %H:%M:%S")
39 ));
40
41 output.push_str("## Summary\n\n");
43 output.push_str(&format!(
44 "**What changed**: {}\n\n",
45 pkg.summary.what_changed
46 ));
47 output.push_str(&format!("**Why**: {}\n\n", pkg.summary.why));
48 output.push_str(&format!("**Impact**: {}\n\n", pkg.summary.impact));
49
50 output.push_str(&format!(
52 "## Changes ({} artifacts)\n\n",
53 pkg.changes.artifacts.len()
54 ));
55
56 let artifacts: Vec<&Artifact> = pkg
57 .changes
58 .artifacts
59 .iter()
60 .filter(|a| matches_file_filters(&a.resource_uri, &ctx.file_filters))
61 .collect();
62
63 for artifact in artifacts {
64 let icon = self.change_icon(&artifact.change_type);
65
66 match ctx.detail_level {
67 DetailLevel::Top => {
68 let summary = artifact
69 .explanation_tiers
70 .as_ref()
71 .map(|t| t.summary.as_str())
72 .or(artifact.rationale.as_deref())
73 .unwrap_or_else(|| {
74 default_summary(&artifact.resource_uri, &artifact.change_type)
75 });
76 output.push_str(&format!(
77 "- {} **{}** — {}\n",
78 icon, artifact.resource_uri, summary
79 ));
80 }
81 DetailLevel::Medium | DetailLevel::Full => {
82 output.push_str(&format!("\n### {} {}\n\n", icon, artifact.resource_uri));
83
84 if let Some(tiers) = &artifact.explanation_tiers {
85 output.push_str(&format!("**Summary**: {}\n\n", tiers.summary));
86 output.push_str(&format!("{}\n\n", tiers.explanation));
87
88 if !tiers.tags.is_empty() {
89 output.push_str(&format!("**Tags**: {}\n\n", tiers.tags.join(", ")));
90 }
91
92 if !tiers.related_artifacts.is_empty() {
93 output.push_str("**Related artifacts**:\n");
94 for related in &tiers.related_artifacts {
95 output.push_str(&format!("- {}\n", related));
96 }
97 output.push('\n');
98 }
99 } else if let Some(rationale) = &artifact.rationale {
100 output.push_str(&format!("**Rationale**: {}\n\n", rationale));
101 }
102
103 if ctx.detail_level == DetailLevel::Full {
104 if let Some(provider) = ctx.diff_provider {
105 match provider.get_diff(&artifact.diff_ref) {
106 Ok(diff) => {
107 output.push_str(
108 "<details>\n<summary>View diff</summary>\n\n```diff\n",
109 );
110 output.push_str(&diff);
111 output.push_str("\n```\n</details>\n\n");
112 }
113 Err(_) => {
114 output.push_str(&format!("*Diff: {}*\n\n", artifact.diff_ref));
115 }
116 }
117 }
118 }
119 }
120 }
121 }
122
123 output.push_str("\n---\n\n");
125 output.push_str(&format!(
126 "🤖 Generated by Trusted Autonomy v{}\n",
127 pkg.package_version
128 ));
129
130 Ok(output)
131 }
132
133 fn name(&self) -> &str {
134 "markdown"
135 }
136}