Skip to main content

ralph_workflow/prompts/
resume_note.rs

1//! Resume context note generation.
2//!
3//! Generates rich context notes for resumed sessions to help agents understand
4//! where they are in the pipeline when resuming from a checkpoint.
5
6use crate::checkpoint::execution_history::StepOutcome;
7use crate::checkpoint::restore::ResumeContext;
8use crate::checkpoint::state::PipelinePhase;
9
10/// Generate a rich resume note from resume context.
11///
12/// Creates a detailed, context-aware note that helps agents understand
13/// where they are in the pipeline when resuming from a checkpoint.
14///
15/// The note includes:
16/// - Phase and iteration information
17/// - Recent execution history (files modified, issues found/fixed)
18/// - Git commits made during the session
19/// - Guidance on what to focus on
20pub fn generate_resume_note(context: &ResumeContext) -> String {
21    let mut note = String::from("SESSION RESUME CONTEXT\n");
22    note.push_str("====================\n\n");
23
24    // Add phase information with specific context based on phase type
25    match context.phase {
26        PipelinePhase::Development => {
27            note.push_str(&format!(
28                "Resuming DEVELOPMENT phase (iteration {} of {})\n",
29                context.iteration + 1,
30                context.total_iterations
31            ));
32        }
33        PipelinePhase::Review => {
34            note.push_str(&format!(
35                "Resuming REVIEW phase (pass {} of {})\n",
36                context.reviewer_pass + 1,
37                context.total_reviewer_passes
38            ));
39        }
40        _ => {
41            note.push_str(&format!("Resuming from phase: {}\n", context.phase_name()));
42        }
43    }
44
45    // Add resume count if this has been resumed before
46    if context.resume_count > 0 {
47        note.push_str(&format!(
48            "This session has been resumed {} time(s)\n",
49            context.resume_count
50        ));
51    }
52
53    // Add rebase state if applicable
54    if !matches!(
55        context.rebase_state,
56        crate::checkpoint::state::RebaseState::NotStarted
57    ) {
58        note.push_str(&format!("Rebase state: {:?}\n", context.rebase_state));
59    }
60
61    note.push('\n');
62
63    // Add execution history summary if available
64    if let Some(ref history) = context.execution_history {
65        if !history.steps.is_empty() {
66            note.push_str("RECENT ACTIVITY:\n");
67            note.push_str("----------------\n");
68
69            // Show recent execution steps (last 5)
70            let recent_steps: Vec<_> = history
71                .steps
72                .iter()
73                .rev()
74                .take(5)
75                .collect::<Vec<_>>()
76                .into_iter()
77                .rev()
78                .collect();
79
80            for step in &recent_steps {
81                note.push_str(&format!(
82                    "- [{}] {} (iteration {}): {}\n",
83                    step.step_type,
84                    step.phase,
85                    step.iteration,
86                    step.outcome.brief_description()
87                ));
88
89                // Add files modified count if available
90                if let Some(ref detail) = step.modified_files_detail {
91                    let total_files =
92                        detail.added.len() + detail.modified.len() + detail.deleted.len();
93                    if total_files > 0 {
94                        note.push_str(&format!("  Files: {} changed", total_files));
95                        if !detail.added.is_empty() {
96                            note.push_str(&format!(" ({} added)", detail.added.len()));
97                        }
98                        if !detail.modified.is_empty() {
99                            note.push_str(&format!(" ({} modified)", detail.modified.len()));
100                        }
101                        if !detail.deleted.is_empty() {
102                            note.push_str(&format!(" ({} deleted)", detail.deleted.len()));
103                        }
104                        note.push('\n');
105                    }
106                }
107
108                // Add issues summary if available
109                if let Some(ref issues) = step.issues_summary {
110                    if issues.found > 0 || issues.fixed > 0 {
111                        note.push_str(&format!(
112                            "  Issues: {} found, {} fixed",
113                            issues.found, issues.fixed
114                        ));
115                        if let Some(ref desc) = issues.description {
116                            note.push_str(&format!(" ({})", desc));
117                        }
118                        note.push('\n');
119                    }
120                }
121
122                // Add git commit if available
123                if let Some(ref oid) = step.git_commit_oid {
124                    note.push_str(&format!("  Commit: {}\n", oid));
125                }
126            }
127
128            note.push('\n');
129        }
130    }
131
132    note.push_str("Previous progress is preserved in git history.\n");
133
134    // Add helpful guidance about what the agent should focus on
135    note.push_str("\nGUIDANCE:\n");
136    note.push_str("--------\n");
137    match context.phase {
138        PipelinePhase::Development => {
139            note.push_str("Continue working on the implementation tasks from your plan.\n");
140        }
141        PipelinePhase::Review => {
142            note.push_str("Review the code changes and provide feedback.\n");
143        }
144        _ => {}
145    }
146
147    note.push('\n');
148    note
149}
150
151/// Helper trait for brief outcome descriptions.
152pub trait BriefDescription {
153    fn brief_description(&self) -> String;
154}
155
156impl BriefDescription for StepOutcome {
157    fn brief_description(&self) -> String {
158        match self {
159            Self::Success {
160                files_modified,
161                output,
162                ..
163            } => {
164                if let Some(ref out) = output {
165                    if !out.is_empty() {
166                        format!("Success - {}", out.lines().next().unwrap_or(""))
167                    } else if !files_modified.is_empty() {
168                        format!("Success - {} files modified", files_modified.len())
169                    } else {
170                        "Success".to_string()
171                    }
172                } else if !files_modified.is_empty() {
173                    format!("Success - {} files modified", files_modified.len())
174                } else {
175                    "Success".to_string()
176                }
177            }
178            Self::Failure {
179                error, recoverable, ..
180            } => {
181                if *recoverable {
182                    format!("Recoverable error - {}", error.lines().next().unwrap_or(""))
183                } else {
184                    format!("Failed - {}", error.lines().next().unwrap_or(""))
185                }
186            }
187            Self::Partial {
188                completed,
189                remaining,
190                ..
191            } => {
192                format!("Partial - {} done, {}", completed, remaining)
193            }
194            Self::Skipped { reason } => {
195                format!("Skipped - {}", reason)
196            }
197        }
198    }
199}