Render the guided walkthrough as bounded text: a verdict line, then one numbered entry per layer — title, rationale, files, contributing changes with their inspect commands — never a hunk dump. The canonical section is rendered verbatim; nothing is re-derived here.
(report: &TriageReport)
| 170 | /// with their inspect commands — never a hunk dump. The canonical section is |
| 171 | /// rendered verbatim; nothing is re-derived here. |
| 172 | pub fn walkthrough_text(report: &TriageReport) -> String { |
| 173 | let mut out = String::new(); |
| 174 | let verdict_label = match report.verdict { |
| 175 | Verdict::Ready => info("READY").to_string(), |
| 176 | Verdict::Blocked => emphasis(crate::output::error("BLOCKED")).to_string(), |
| 177 | Verdict::Stale => crate::output::warning("STALE").to_string(), |
| 178 | }; |
| 179 | out.push_str(&format!( |
| 180 | "{} {} \u{2192} {} {} ({} layer{}, {} change{})\n", |
| 181 | emphasis("walkthrough"), |
| 182 | report.inputs.feature, |
| 183 | report.inputs.target, |
| 184 | verdict_label, |
| 185 | report.walkthrough.len(), |
| 186 | if report.walkthrough.len() == 1 { |
| 187 | "" |
| 188 | } else { |
| 189 | "s" |
| 190 | }, |
| 191 | report.summary.changes, |
| 192 | if report.summary.changes == 1 { "" } else { "s" }, |
| 193 | )); |
| 194 | |
| 195 | if report.walkthrough.is_empty() { |
| 196 | out.push_str("\n no walkthrough: the candidate set modifies no files\n"); |
| 197 | return out; |
| 198 | } |
| 199 | |
| 200 | // The exact inspect command per contributing change, from the canonical |
| 201 | // change reports (no re-derivation). |
| 202 | let review_cmd: BTreeMap<&str, &str> = report |
| 203 | .changes |
| 204 | .iter() |
| 205 | .map(|c| (c.id.as_str(), c.review_command.as_str())) |
| 206 | .collect(); |
| 207 | |
| 208 | for (i, layer) in report.walkthrough.iter().enumerate() { |
| 209 | out.push_str(&format!("\n{}. {}\n", i + 1, emphasis(&layer.title))); |
| 210 | out.push_str(&format!(" {}\n", layer.rationale)); |
| 211 | let files: Vec<String> = layer |
| 212 | .files |
| 213 | .iter() |
| 214 | .map(|f| f.strip_prefix("file:").unwrap_or(f).to_string()) |
| 215 | .collect(); |
| 216 | out.push_str(&format!(" files: {}\n", capped_list(&files))); |
| 217 | if !layer.criteria.is_empty() { |
| 218 | out.push_str(&format!(" criteria: {}\n", capped_list(&layer.criteria))); |
| 219 | } |
| 220 | for ch in &layer.changes { |
| 221 | let short: String = ch.chars().take(12).collect(); |
| 222 | match review_cmd.get(ch.as_str()) { |
| 223 | Some(cmd) => out.push_str(&format!(" {short} {}\n", hint(cmd))), |
| 224 | None => out.push_str(&format!(" {short}\n")), |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | out.push_str(&format!("\n{} {}\n", hint("ref"), hint(&report.reference))); |