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}