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    pub fn new(base_log_dir: &str, workspace: &dyn Workspace) -> std::io::Result<Self> {
26        let timestamp = Local::now().format("%Y%m%d_%H%M%S");
27        let run_dir = PathBuf::from(base_log_dir).join(format!("run_{timestamp}"));
28        workspace.create_dir_all(&run_dir)?;
29
30        Ok(Self {
31            run_dir,
32            attempt_counter: 0,
33        })
34    }
35
36    /// Create a no-op logging session that discards all writes.
37    ///
38    /// This is used as a fallback when all log directories fail to be created.
39    /// The session will still track attempt numbers and provide a dummy run_dir,
40    /// but writes will silently succeed without actually writing anything.
41    ///
42    /// # Returns
43    ///
44    /// A `CommitLogSession` that uses `/dev/null` equivalent as its run directory.
45    pub fn noop() -> Self {
46        // Use a path that indicates this is a noop session
47        // The path won't be created or written to by noop session
48        Self {
49            run_dir: PathBuf::from("/dev/null/ralph-noop-session"),
50            attempt_counter: 0,
51        }
52    }
53
54    /// Check if this is a no-op session.
55    pub fn is_noop(&self) -> bool {
56        self.run_dir.starts_with("/dev/null")
57    }
58
59    /// Get the path to the run directory.
60    pub fn run_dir(&self) -> &Path {
61        &self.run_dir
62    }
63
64    /// Get the next attempt number and increment the counter.
65    pub const fn next_attempt_number(&mut self) -> usize {
66        self.attempt_counter += 1;
67        self.attempt_counter
68    }
69
70    /// Create a new attempt log for this session.
71    ///
72    /// # Arguments
73    ///
74    /// * `agent` - The agent being used
75    /// * `strategy` - The retry strategy being used
76    pub fn new_attempt(&mut self, agent: &str, strategy: &str) -> CommitAttemptLog {
77        let attempt_number = self.next_attempt_number();
78        CommitAttemptLog::new(attempt_number, agent, strategy)
79    }
80
81    /// Write summary file at end of session.
82    ///
83    /// For noop sessions, this silently succeeds without writing anything.
84    ///
85    /// # Arguments
86    ///
87    /// * `total_attempts` - Total number of attempts made
88    /// * `final_outcome` - Description of the final outcome
89    /// * `workspace` - The workspace to use for filesystem operations
90    pub fn write_summary(
91        &self,
92        total_attempts: usize,
93        final_outcome: &str,
94        workspace: &dyn Workspace,
95    ) -> std::io::Result<()> {
96        // Skip writing for noop sessions
97        if self.is_noop() {
98            return Ok(());
99        }
100
101        use std::fmt::Write;
102
103        let summary_path = self.run_dir.join("SUMMARY.txt");
104        let mut content = String::new();
105
106        let _ = writeln!(content, "COMMIT GENERATION SESSION SUMMARY");
107        let _ = writeln!(content, "=================================");
108        let _ = writeln!(content);
109        let _ = writeln!(content, "Run directory: {}", self.run_dir.display());
110        let _ = writeln!(content, "Total attempts: {total_attempts}");
111        let _ = writeln!(content, "Final outcome: {final_outcome}");
112        let _ = writeln!(content);
113        let _ = writeln!(content, "Individual attempt logs are in this directory.");
114
115        workspace.write(&summary_path, &content)?;
116        Ok(())
117    }
118}