Skip to main content

ralph_workflow/phases/commit_logging/
file_logging.rs

1// File-based logging operations - writing to log files, file management.
2// This file is included via include!() macro from the parent commit_logging.rs module.
3
4/// Session tracker for commit generation logging.
5///
6/// Manages a unique run directory for a commit generation session,
7/// ensuring log files are organized and don't overwrite each other.
8#[derive(Debug)]
9pub struct CommitLogSession {
10    /// Base log directory
11    run_dir: PathBuf,
12    /// Current attempt counter
13    attempt_counter: usize,
14}
15
16impl CommitLogSession {
17    /// Create a new logging session using workspace abstraction.
18    ///
19    /// Creates a unique run directory under the base log path.
20    ///
21    /// # Arguments
22    ///
23    /// * `base_log_dir` - Base directory for commit logs (e.g., `.agent/logs/commit_generation`)
24    /// * `workspace` - The workspace to use for filesystem operations
25    ///
26    /// # Errors
27    ///
28    /// Returns error if the operation fails.
29    pub fn new(base_log_dir: &str, workspace: &dyn Workspace) -> std::io::Result<Self> {
30        let timestamp = Local::now().format("%Y%m%d_%H%M%S");
31        let run_dir = PathBuf::from(base_log_dir).join(format!("run_{timestamp}"));
32        workspace.create_dir_all(&run_dir)?;
33
34        Ok(Self {
35            run_dir,
36            attempt_counter: 0,
37        })
38    }
39
40    /// Create a no-op logging session that discards all writes.
41    ///
42    /// This is used as a fallback when all log directories fail to be created.
43    /// The session will still track attempt numbers and provide a dummy `run_dir`,
44    /// but writes will silently succeed without actually writing anything.
45    ///
46    /// # Returns
47    ///
48    /// A `CommitLogSession` that uses `/dev/null` equivalent as its run directory.
49    #[must_use] 
50    pub fn noop() -> Self {
51        // Use a path that indicates this is a noop session
52        // The path won't be created or written to by noop session
53        Self {
54            run_dir: PathBuf::from("/dev/null/ralph-noop-session"),
55            attempt_counter: 0,
56        }
57    }
58
59    /// Check if this is a no-op session.
60    #[must_use] 
61    pub fn is_noop(&self) -> bool {
62        self.run_dir.starts_with("/dev/null")
63    }
64
65    /// Get the path to the run directory.
66    #[must_use] 
67    pub fn run_dir(&self) -> &Path {
68        &self.run_dir
69    }
70
71    /// Get the next attempt number and increment the counter.
72    pub const fn next_attempt_number(&mut self) -> usize {
73        self.attempt_counter += 1;
74        self.attempt_counter
75    }
76
77    /// Create a new attempt log for this session.
78    ///
79    /// # Arguments
80    ///
81    /// * `agent` - The agent being used
82    /// * `strategy` - The retry strategy being used
83    pub fn new_attempt(&mut self, agent: &str, strategy: &str) -> CommitAttemptLog {
84        let attempt_number = self.next_attempt_number();
85        CommitAttemptLog::new(attempt_number, agent, strategy)
86    }
87
88    /// Write summary file at end of session.
89    ///
90    /// For noop sessions, this silently succeeds without writing anything.
91    ///
92    /// # Arguments
93    ///
94    /// * `total_attempts` - Total number of attempts made
95    /// * `final_outcome` - Description of the final outcome
96    /// * `workspace` - The workspace to use for filesystem operations
97    ///
98    /// # Errors
99    ///
100    /// Returns error if the operation fails.
101    pub fn write_summary(
102        &self,
103        total_attempts: usize,
104        final_outcome: &str,
105        workspace: &dyn Workspace,
106    ) -> std::io::Result<()> {
107        use std::fmt::Write;
108
109        // Skip writing for noop sessions
110        if self.is_noop() {
111            return Ok(());
112        }
113
114        let summary_path = self.run_dir.join("SUMMARY.txt");
115        let mut content = String::new();
116
117        let _ = writeln!(content, "COMMIT GENERATION SESSION SUMMARY");
118        let _ = writeln!(content, "=================================");
119        let _ = writeln!(content);
120        let _ = writeln!(content, "Run directory: {}", self.run_dir.display());
121        let _ = writeln!(content, "Total attempts: {total_attempts}");
122        let _ = writeln!(content, "Final outcome: {final_outcome}");
123        let _ = writeln!(content);
124        let _ = writeln!(content, "Individual attempt logs are in this directory.");
125
126        workspace.write(&summary_path, &content)?;
127        Ok(())
128    }
129}