ralph_workflow/pipeline/
mod.rs

1//! Pipeline Execution Module
2//!
3//! This module contains the core pipeline execution infrastructure:
4//! - Types for tracking pipeline statistics and RAII cleanup
5//! - Model flag resolution utilities
6//! - Command execution helpers with fault-tolerant fallback chains
7//! - Timer utilities for tracking execution duration
8//! - Session management for agent continuation
9//! - Log file path management
10//!
11//! # Module Structure
12//!
13//! - [`model_flag`] - Model flag resolution and provider detection
14//! - [`runner`] - Pipeline runtime and command execution with fallback
15//! - [`types`] - Pipeline statistics tracking and RAII guards
16//! - [`session`] - Session extraction and continuation for XSD retries
17//! - [`logfile`] - Unified log file path creation, parsing, and discovery
18
19#![deny(unsafe_code)]
20
21mod clipboard;
22mod fallback;
23pub mod logfile;
24mod model_flag;
25mod prompt;
26mod runner;
27pub mod session;
28mod types;
29
30#[cfg(any(test, feature = "test-utils"))]
31pub mod test_trait;
32
33pub use fallback::OutputValidator;
34pub use prompt::{run_with_prompt, PipelineRuntime, PromptCommand};
35#[cfg(test)]
36pub use runner::run_with_fallback;
37pub use runner::{
38    run_with_fallback_and_validator, run_xsd_retry_with_session, FallbackConfig, XsdRetryConfig,
39};
40pub use types::{AgentPhaseGuard, Stats};
41
42// ===== Timer Utilities =====
43
44use std::time::{Duration, Instant};
45
46/// Timer for tracking execution duration
47#[derive(Clone)]
48pub struct Timer {
49    start_time: Instant,
50    phase_start: Instant,
51}
52
53impl Timer {
54    /// Create a new timer, starting now
55    pub fn new() -> Self {
56        let now = Instant::now();
57        Self {
58            start_time: now,
59            phase_start: now,
60        }
61    }
62
63    /// Start a new phase timer
64    pub fn start_phase(&mut self) {
65        self.phase_start = Instant::now();
66    }
67
68    /// Get elapsed time since timer start
69    pub fn elapsed(&self) -> Duration {
70        self.start_time.elapsed()
71    }
72
73    /// Get elapsed time since phase start
74    pub fn phase_elapsed(&self) -> Duration {
75        self.phase_start.elapsed()
76    }
77
78    /// Format a duration as "Xm YYs"
79    pub fn format_duration(duration: Duration) -> String {
80        let total_secs = duration.as_secs();
81        let mins = total_secs / 60;
82        let secs = total_secs % 60;
83        format!("{mins}m {secs:02}s")
84    }
85
86    /// Get formatted elapsed time since start
87    pub fn elapsed_formatted(&self) -> String {
88        Self::format_duration(self.elapsed())
89    }
90
91    /// Get formatted elapsed time since phase start
92    pub fn phase_elapsed_formatted(&self) -> String {
93        Self::format_duration(self.phase_elapsed())
94    }
95}
96
97impl Default for Timer {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[cfg(test)]
104mod timer_tests {
105    use super::*;
106    use std::thread;
107
108    #[test]
109    fn test_format_duration_zero() {
110        let d = Duration::from_secs(0);
111        assert_eq!(Timer::format_duration(d), "0m 00s");
112    }
113
114    #[test]
115    fn test_format_duration_seconds() {
116        let d = Duration::from_secs(30);
117        assert_eq!(Timer::format_duration(d), "0m 30s");
118    }
119
120    #[test]
121    fn test_format_duration_minutes() {
122        let d = Duration::from_secs(65);
123        assert_eq!(Timer::format_duration(d), "1m 05s");
124    }
125
126    #[test]
127    fn test_format_duration_large() {
128        let d = Duration::from_secs(3661);
129        assert_eq!(Timer::format_duration(d), "61m 01s");
130    }
131
132    #[test]
133    fn test_timer_elapsed() {
134        let timer = Timer::new();
135        thread::sleep(Duration::from_millis(10));
136        assert!(timer.elapsed() >= Duration::from_millis(10));
137    }
138
139    #[test]
140    fn test_timer_phase() {
141        let mut timer = Timer::new();
142        thread::sleep(Duration::from_millis(10));
143        timer.start_phase();
144        thread::sleep(Duration::from_millis(10));
145        // Phase elapsed should be less than total elapsed
146        assert!(timer.phase_elapsed() < timer.elapsed());
147    }
148}
149
150#[cfg(test)]
151mod tests;