Skip to main content

codex_protocol/
review_format.rs

1use crate::protocol::ReviewFinding;
2use crate::protocol::ReviewOutputEvent;
3
4// These helpers return plain strings that higher layers may style as needed.
5
6fn format_location(item: &ReviewFinding) -> String {
7    let path = item.code_location.absolute_file_path.display();
8    let start = item.code_location.line_range.start;
9    let end = item.code_location.line_range.end;
10    format!("{path}:{start}-{end}")
11}
12
13/// Fallback text used when a review contains no displayable output.
14pub const REVIEW_FALLBACK_MESSAGE: &str = "Reviewer failed to output a response.";
15
16/// Format a full review findings block as plain text lines.
17///
18/// - When `selection` is `Some`, each item line includes a checkbox marker:
19///   "[x]" for selected items and "[ ]" for unselected. Missing indices
20///   default to selected.
21/// - When `selection` is `None`, the marker is omitted and a simple bullet is
22///   rendered ("- Title — path:start-end").
23pub fn format_review_findings_block(
24    findings: &[ReviewFinding],
25    selection: Option<&[bool]>,
26) -> String {
27    let mut lines: Vec<String> = Vec::new();
28    lines.push(String::new());
29
30    // Header
31    if findings.len() > 1 {
32        lines.push("Full review comments:".to_string());
33    } else {
34        lines.push("Review comment:".to_string());
35    }
36
37    for (idx, item) in findings.iter().enumerate() {
38        lines.push(String::new());
39
40        let title = &item.title;
41        let location = format_location(item);
42
43        if let Some(flags) = selection {
44            // Default to selected if index is out of bounds.
45            let checked = flags.get(idx).copied().unwrap_or(true);
46            let marker = if checked { "[x]" } else { "[ ]" };
47            lines.push(format!("- {marker} {title} — {location}"));
48        } else {
49            lines.push(format!("- {title} — {location}"));
50        }
51
52        for body_line in item.body.lines() {
53            lines.push(format!("  {body_line}"));
54        }
55    }
56
57    lines.join("\n")
58}
59
60/// Render a human-readable review summary suitable for a user-facing message.
61///
62/// Returns either the explanation, the formatted findings block, or both
63/// separated by a blank line. If neither is present, emits a fallback message.
64pub fn render_review_output_text(output: &ReviewOutputEvent) -> String {
65    let mut sections = Vec::new();
66    let explanation = output.overall_explanation.trim();
67    if !explanation.is_empty() {
68        sections.push(explanation.to_string());
69    }
70    if !output.findings.is_empty() {
71        let findings = format_review_findings_block(&output.findings, /*selection*/ None);
72        let trimmed = findings.trim();
73        if !trimmed.is_empty() {
74            sections.push(trimmed.to_string());
75        }
76    }
77    if sections.is_empty() {
78        REVIEW_FALLBACK_MESSAGE.to_string()
79    } else {
80        sections.join("\n\n")
81    }
82}