//! Markdown renderer for the refactor plan. use crate::plan::{Plan, PlanItem}; pub fn render(plan: &Plan, branch: Option<&str>) -> String { let mut out = String::new(); out.push_str("# Deep-sleep refactor plan\n\n"); if let Some(b) = branch { out.push_str(&format!("Proposed fork branch: `{}`\n\n", b)); } out.push_str(&summary(plan)); out.push_str(&auto_section(plan)); out.push_str(&manual_section(plan)); out.push_str(&footer()); out } fn summary(plan: &Plan) -> String { let total = plan.items.len(); let auto = plan.auto_items().len(); let manual = plan.manual_items().len(); format!( "## Summary\n\n\ - Total conflicts: **{total}**\n\ - Auto-apply candidates: **{auto}**\n\ - Requires human decision (zero-conflict guarantee excludes these from patch): **{manual}**\n\n", ) } fn auto_section(plan: &Plan) -> String { let items = plan.auto_items(); if items.is_empty() { return "## Auto-apply\n\n_No safe auto-apply changes this cycle._\n\n".to_string(); } let mut s = String::from("## Auto-apply (engine-proposed; review before merge)\n\n"); for (i, item) in items.iter().enumerate() { s.push_str(&item_block(i + 1, item)); } s } fn manual_section(plan: &Plan) -> String { let items = plan.manual_items(); if items.is_empty() { return "## Requires human decision\n\n_None this cycle._\n\n".to_string(); } let mut s = String::from("## Requires human decision (NOT in patch)\n\n"); for (i, item) in items.iter().enumerate() { s.push_str(&item_block(i + 1, item)); } s } fn item_block(n: usize, item: &PlanItem) -> String { format!( "### {n}. [{cat}/{sev}] {files}\n\n\ - **Why:** {why}\n\ - **Example:** {ex}\n\ - **Tradeoff:** {tr}\n\n", n = n, cat = item.category, sev = item.severity, files = item.files.join(" + "), why = item.why, ex = item.example, tr = item.tradeoff, ) } fn footer() -> String { "---\n\n\ Generated by `kei-refactor-engine` (v0.13.0). Zero-conflict guarantee: \ no item above marked `requires human decision` appears in the companion \ patch file.\n" .to_string() }