ralph_workflow/phases/
development.rs1use crate::files::llm_output_extraction::PlanElements;
11
12pub(crate) fn format_plan_as_markdown(elements: &PlanElements) -> String {
14 let mut result = String::new();
15
16 result.push_str("## Summary\n\n");
18 result.push_str(&elements.summary.context);
19 result.push_str("\n\n");
20
21 result.push_str("### Scope\n\n");
23 for item in &elements.summary.scope_items {
24 if let Some(ref count) = item.count {
25 result.push_str(&format!("- **{}** {}", count, item.description));
26 } else {
27 result.push_str(&format!("- {}", item.description));
28 }
29 if let Some(ref category) = item.category {
30 result.push_str(&format!(" ({})", category));
31 }
32 result.push('\n');
33 }
34 result.push('\n');
35
36 result.push_str("## Implementation Steps\n\n");
38 for step in &elements.steps {
39 let step_type_str = match step.step_type {
41 crate::files::llm_output_extraction::xsd_validation_plan::StepType::FileChange => {
42 "file-change"
43 }
44 crate::files::llm_output_extraction::xsd_validation_plan::StepType::Action => "action",
45 crate::files::llm_output_extraction::xsd_validation_plan::StepType::Research => {
46 "research"
47 }
48 };
49 let priority_str = step.priority.map_or(String::new(), |p| {
50 format!(
51 " [{}]",
52 match p {
53 crate::files::llm_output_extraction::xsd_validation_plan::Priority::Critical =>
54 "critical",
55 crate::files::llm_output_extraction::xsd_validation_plan::Priority::High =>
56 "high",
57 crate::files::llm_output_extraction::xsd_validation_plan::Priority::Medium =>
58 "medium",
59 crate::files::llm_output_extraction::xsd_validation_plan::Priority::Low =>
60 "low",
61 }
62 )
63 });
64
65 result.push_str(&format!(
66 "### Step {} ({}){}: {}\n\n",
67 step.number, step_type_str, priority_str, step.title
68 ));
69
70 if !step.target_files.is_empty() {
72 result.push_str("**Target Files:**\n");
73 for tf in &step.target_files {
74 let action_str = match tf.action {
75 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Create => {
76 "create"
77 }
78 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Modify => {
79 "modify"
80 }
81 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Delete => {
82 "delete"
83 }
84 };
85 result.push_str(&format!("- `{}` ({})\n", tf.path, action_str));
86 }
87 result.push('\n');
88 }
89
90 if let Some(ref location) = step.location {
92 result.push_str(&format!("**Location:** {}\n\n", location));
93 }
94
95 if let Some(ref rationale) = step.rationale {
97 result.push_str(&format!("**Rationale:** {}\n\n", rationale));
98 }
99
100 result.push_str(&format_rich_content(&step.content));
102 result.push('\n');
103
104 if !step.depends_on.is_empty() {
106 result.push_str("**Depends on:** ");
107 let deps: Vec<String> = step
108 .depends_on
109 .iter()
110 .map(|d| format!("Step {}", d))
111 .collect();
112 result.push_str(&deps.join(", "));
113 result.push_str("\n\n");
114 }
115 }
116
117 result.push_str("## Critical Files\n\n");
119 result.push_str("### Primary Files\n\n");
120 for pf in &elements.critical_files.primary_files {
121 let action_str = match pf.action {
122 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Create => {
123 "create"
124 }
125 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Modify => {
126 "modify"
127 }
128 crate::files::llm_output_extraction::xsd_validation_plan::FileAction::Delete => {
129 "delete"
130 }
131 };
132 if let Some(ref est) = pf.estimated_changes {
133 result.push_str(&format!("- `{}` ({}) - {}\n", pf.path, action_str, est));
134 } else {
135 result.push_str(&format!("- `{}` ({})\n", pf.path, action_str));
136 }
137 }
138 result.push('\n');
139
140 if !elements.critical_files.reference_files.is_empty() {
141 result.push_str("### Reference Files\n\n");
142 for rf in &elements.critical_files.reference_files {
143 result.push_str(&format!("- `{}` - {}\n", rf.path, rf.purpose));
144 }
145 result.push('\n');
146 }
147
148 result.push_str("## Risks & Mitigations\n\n");
150 for rp in &elements.risks_mitigations {
151 let severity_str = rp.severity.map_or(String::new(), |s| {
152 format!(
153 " [{}]",
154 match s {
155 crate::files::llm_output_extraction::xsd_validation_plan::Severity::Low =>
156 "low",
157 crate::files::llm_output_extraction::xsd_validation_plan::Severity::Medium =>
158 "medium",
159 crate::files::llm_output_extraction::xsd_validation_plan::Severity::High =>
160 "high",
161 crate::files::llm_output_extraction::xsd_validation_plan::Severity::Critical =>
162 "critical",
163 }
164 )
165 });
166 result.push_str(&format!("**Risk{}:** {}\n", severity_str, rp.risk));
167 result.push_str(&format!("**Mitigation:** {}\n\n", rp.mitigation));
168 }
169
170 result.push_str("## Verification Strategy\n\n");
172 for (i, v) in elements.verification_strategy.iter().enumerate() {
173 result.push_str(&format!("{}. **{}**\n", i + 1, v.method));
174 result.push_str(&format!(" Expected: {}\n\n", v.expected_outcome));
175 }
176
177 result
178}
179
180fn format_rich_content(
182 content: &crate::files::llm_output_extraction::xsd_validation_plan::RichContent,
183) -> String {
184 use crate::files::llm_output_extraction::xsd_validation_plan::ContentElement;
185
186 let mut result = String::new();
187
188 for element in &content.elements {
189 match element {
190 ContentElement::Paragraph(p) => {
191 result.push_str(&format_inline_content(&p.content));
192 result.push_str("\n\n");
193 }
194 ContentElement::CodeBlock(cb) => {
195 let lang = cb.language.as_deref().unwrap_or("");
196 result.push_str(&format!("```{}\n", lang));
197 result.push_str(&cb.content);
198 if !cb.content.ends_with('\n') {
199 result.push('\n');
200 }
201 result.push_str("```\n\n");
202 }
203 ContentElement::Table(t) => {
204 if let Some(ref caption) = t.caption {
205 result.push_str(&format!("**{}**\n\n", caption));
206 }
207 if !t.columns.is_empty() {
209 result.push_str("| ");
210 result.push_str(&t.columns.join(" | "));
211 result.push_str(" |\n");
212 result.push('|');
213 for _ in &t.columns {
214 result.push_str(" --- |");
215 }
216 result.push('\n');
217 } else if let Some(first_row) = t.rows.first() {
218 result.push('|');
220 for _ in &first_row.cells {
221 result.push_str(" --- |");
222 }
223 result.push('\n');
224 }
225 for row in &t.rows {
227 result.push_str("| ");
228 let cells: Vec<String> = row
229 .cells
230 .iter()
231 .map(|c| format_inline_content(&c.content))
232 .collect();
233 result.push_str(&cells.join(" | "));
234 result.push_str(" |\n");
235 }
236 result.push('\n');
237 }
238 ContentElement::List(l) => {
239 result.push_str(&format_list(l, 0));
240 result.push('\n');
241 }
242 ContentElement::Heading(h) => {
243 let prefix = "#".repeat(h.level as usize);
244 result.push_str(&format!("{} {}\n\n", prefix, h.text));
245 }
246 }
247 }
248
249 result
250}
251
252fn format_inline_content(
254 content: &[crate::files::llm_output_extraction::xsd_validation_plan::InlineElement],
255) -> String {
256 use crate::files::llm_output_extraction::xsd_validation_plan::InlineElement;
257
258 content
259 .iter()
260 .map(|e| match e {
261 InlineElement::Text(s) => s.clone(),
262 InlineElement::Emphasis(s) => format!("**{}**", s),
263 InlineElement::Code(s) => format!("`{}`", s),
264 InlineElement::Link { href, text } => format!("[{}]({})", text, href),
265 })
266 .collect::<Vec<_>>()
267 .join("")
268}
269
270fn format_list(
272 list: &crate::files::llm_output_extraction::xsd_validation_plan::List,
273 indent: usize,
274) -> String {
275 use crate::files::llm_output_extraction::xsd_validation_plan::ListType;
276
277 let mut result = String::new();
278 let indent_str = " ".repeat(indent);
279
280 for (i, item) in list.items.iter().enumerate() {
281 let marker = match list.list_type {
282 ListType::Ordered => format!("{}. ", i + 1),
283 ListType::Unordered => "- ".to_string(),
284 };
285
286 result.push_str(&indent_str);
287 result.push_str(&marker);
288 result.push_str(&format_inline_content(&item.content));
289 result.push('\n');
290
291 if let Some(ref nested) = item.nested_list {
292 result.push_str(&format_list(nested, indent + 1));
293 }
294 }
295
296 result
297}