1use perspt_sdk::error::Result;
8use perspt_sdk::prompt::{BoundedList, BoundedText, PromptSection};
9use perspt_sdk::CorrectionPacket;
10
11mod generated {
12 include!(concat!(env!("OUT_DIR"), "/prompt_sections.rs"));
13}
14
15pub use generated::branch_correct;
16
17fn bounded_items(
18 items: impl IntoIterator<Item = String>,
19 count: usize,
20 bytes: usize,
21) -> Vec<String> {
22 items
23 .into_iter()
24 .map(|item| {
25 let mut item = item;
26 if item.len() > bytes {
27 let mut end = bytes.saturating_sub(1);
28 while end > 0 && !item.is_char_boundary(end) {
29 end -= 1;
30 }
31 item.truncate(end);
32 item.push('…');
33 }
34 item
35 })
36 .take(count)
37 .collect()
38}
39
40pub fn render_correction(packet: &CorrectionPacket) -> Result<String> {
42 let diagnostics = bounded_items(
43 packet.diagnostics.iter().map(|diagnostic| {
44 let location = match (&diagnostic.path, diagnostic.line) {
45 (Some(path), Some(line)) => format!("{path}:{line}: "),
46 (Some(path), None) => format!("{path}: "),
47 _ => String::new(),
48 };
49 let code = diagnostic
50 .code
51 .as_deref()
52 .map(|code| format!("[{code}] "))
53 .unwrap_or_default();
54 format!("{location}{code}{}", diagnostic.message)
55 }),
56 32,
57 500,
58 );
59 let operators = bounded_items(
60 packet.operators.iter().map(|operator| {
61 if operator.rationale.is_empty() {
62 operator.instruction.clone()
63 } else {
64 format!("{} ({})", operator.instruction, operator.rationale)
65 }
66 }),
67 8,
68 1000,
69 );
70 let mut summary = packet.dominant_cluster.root_cause.clone();
71 summary.truncate(1000);
72 let sections = [
73 branch_correct::Role {}.render()?,
74 branch_correct::Correction {
75 cluster_summary: BoundedText::new(summary)?,
76 diagnostics: BoundedList::new(diagnostics)?,
77 paths: BoundedList::new(bounded_items(
78 packet.affected.paths.iter().cloned(),
79 32,
80 250,
81 ))?,
82 symbols: BoundedList::new(bounded_items(
83 packet
84 .affected
85 .symbols
86 .iter()
87 .map(|symbol| symbol.name.clone()),
88 32,
89 120,
90 ))?,
91 operators: BoundedList::new(operators)?,
92 }
93 .render()?,
94 branch_correct::OutputContract {}.render()?,
95 ];
96 Ok(sections
97 .iter()
98 .map(|section| section.content.as_str())
99 .collect::<Vec<_>>()
100 .join(branch_correct::SEPARATOR))
101}