Skip to main content

ralph_workflow/reducer/event/
commit.rs

1//! Commit generation events.
2//!
3//! Events related to commit message generation, validation, and creation.
4
5use serde::{Deserialize, Serialize};
6
7/// Serializable representation of a process execution result for checkpoint persistence.
8///
9/// This is a domain-shaped outcome carrying raw process results without policy interpretation.
10/// The exit code, stdout, and stderr are preserved for reducer/orchestrator policy decisions.
11#[derive(Clone, Serialize, Deserialize, Debug)]
12pub struct ProcessExecutionResult {
13    /// Process exit code (0 = success, non-zero = failure per convention).
14    pub exit_code: i32,
15    /// Standard output captured from the process.
16    pub stdout: String,
17    /// Standard error captured from the process.
18    pub stderr: String,
19}
20
21impl From<crate::executor::ProcessOutput> for ProcessExecutionResult {
22    fn from(output: crate::executor::ProcessOutput) -> Self {
23        Self {
24            exit_code: output.exit_code(),
25            stdout: output.stdout,
26            stderr: output.stderr,
27        }
28    }
29}
30
31/// Commit generation events.
32///
33/// Events related to commit message generation, validation, and creation.
34/// Commit generation occurs after development iterations and review fixes.
35///
36/// # State Machine
37///
38/// ```text
39/// NotStarted -> Generating -> Generated -> Committed
40///                    |              |
41///                    +--> (retry) --+
42///                    |
43///                    +--> Skipped
44/// ```
45///
46/// # Emitted By
47///
48/// - Commit generation handlers in `handler/commit/`
49/// - Commit message validation handlers
50/// - Git commit handlers
51#[derive(Clone, Serialize, Deserialize, Debug)]
52pub enum CommitEvent {
53    /// Commit message generation started.
54    GenerationStarted,
55    /// Commit diff computed for commit generation.
56    ///
57    /// Emitted after preparing the diff that will be committed. The reducer
58    /// uses the `empty` flag to decide whether to skip commit creation.
59    DiffPrepared {
60        /// True when the diff is empty.
61        empty: bool,
62        /// Content identifier (sha256 hex) of the prepared diff content.
63        ///
64        /// This is used to guard against reusing stale materialized inputs when the
65        /// diff content changes across checkpoints or retries.
66        content_id_sha256: String,
67    },
68    /// Commit diff computation failed.
69    DiffFailed {
70        /// The error message for the diff failure.
71        error: String,
72    },
73    /// Commit diff is no longer available and must be recomputed.
74    ///
75    /// This is used for recoverability when `.agent/tmp` artifacts are cleaned between
76    /// checkpoints or when required diff files go missing during resume.
77    DiffInvalidated {
78        /// Reason for invalidation.
79        reason: String,
80    },
81    /// Commit prompt prepared for a commit attempt.
82    PromptPrepared {
83        /// The attempt number.
84        attempt: u32,
85    },
86    /// Commit agent invoked for a commit attempt.
87    AgentInvoked {
88        /// The attempt number.
89        attempt: u32,
90    },
91    /// Commit message XML extracted for a commit attempt.
92    CommitXmlExtracted {
93        /// The attempt number.
94        attempt: u32,
95    },
96    /// Commit message XML missing for a commit attempt.
97    CommitXmlMissing {
98        /// The attempt number.
99        attempt: u32,
100    },
101    /// Commit message XML validated successfully.
102    CommitXmlValidated {
103        /// The generated commit message.
104        message: String,
105        /// The attempt number.
106        attempt: u32,
107        /// Optional list of files to selectively commit.
108        ///
109        /// Empty means commit all changed files.
110        /// Defaults to empty for backward compatibility with old checkpoints.
111        #[serde(default)]
112        files: Vec<String>,
113        /// Files excluded from this commit with documented reasons.
114        ///
115        /// Audit/observability metadata only — does not affect commit execution.
116        /// Defaults to empty for backward compatibility with old checkpoints.
117        #[serde(default)]
118        excluded_files: Vec<crate::reducer::state::pipeline::ExcludedFile>,
119    },
120    /// Commit message XML validation failed.
121    CommitXmlValidationFailed {
122        /// The reason for validation failure.
123        reason: String,
124        /// The attempt number.
125        attempt: u32,
126    },
127    /// Commit message XML archived.
128    CommitXmlArchived {
129        /// The attempt number.
130        attempt: u32,
131    },
132    /// Commit message XML cleaned before invoking the commit agent.
133    CommitXmlCleaned {
134        /// The attempt number.
135        attempt: u32,
136    },
137    /// Commit message was generated.
138    MessageGenerated {
139        /// The generated commit message.
140        message: String,
141        /// The attempt number.
142        attempt: u32,
143    },
144    /// Commit message validation failed.
145    MessageValidationFailed {
146        /// The reason for validation failure.
147        reason: String,
148        /// The attempt number that failed.
149        attempt: u32,
150    },
151    /// Commit was created successfully.
152    Created {
153        /// The commit hash.
154        hash: String,
155        /// The commit message used.
156        message: String,
157    },
158
159    // === Cloud mode git remote operations (emitted only when cloud mode is enabled) ===
160    /// Git authentication configured successfully for remote operations.
161    GitAuthConfigured,
162
163    /// Push to remote executed (boundary emits raw process output).
164    ///
165    /// Domain-shaped execution outcome carrying the raw git push result.
166    /// The reducer/orchestrator interprets this outcome and emits policy-level
167    /// success/failure events (PushCompleted/PushFailed).
168    PushExecuted {
169        remote: String,
170        branch: String,
171        commit_sha: String,
172        /// Raw process execution result from git push.
173        /// Contains exit code, stdout, and stderr for policy interpretation.
174        result: ProcessExecutionResult,
175    },
176
177    /// Push to remote completed successfully.
178    PushCompleted {
179        remote: String,
180        branch: String,
181        commit_sha: String,
182    },
183
184    /// Push to remote failed.
185    PushFailed {
186        remote: String,
187        branch: String,
188        error: String,
189    },
190
191    /// Pull request created successfully.
192    PullRequestCreated { url: String, number: u32 },
193
194    /// Pull request creation failed.
195    PullRequestFailed { error: String },
196    /// Commit generation failed completely.
197    GenerationFailed {
198        /// The reason for failure.
199        reason: String,
200    },
201    /// Commit was skipped (e.g., no changes to commit).
202    Skipped {
203        /// The reason for skipping.
204        reason: String,
205    },
206
207    /// Pre-termination commit safety check completed successfully.
208    ///
209    /// Emitted after `Effect::CheckUncommittedChangesBeforeTermination` when the
210    /// working directory is clean, allowing termination to proceed.
211    PreTerminationSafetyCheckPassed,
212
213    /// Pre-termination commit safety check detected uncommitted changes.
214    ///
215    /// This is not a terminal error: the reducer must route back through the
216    /// commit phase so the changes are committed (or explicitly skipped).
217    PreTerminationUncommittedChangesDetected {
218        /// Number of lines in `git status --porcelain` output.
219        file_count: usize,
220    },
221
222    /// Residual uncommitted files detected after a selective commit pass.
223    ///
224    /// When `pass` is below the configured retry limit, triggers the next automatic
225    /// commit retry pass. When `pass` reaches the final retry pass, the files are
226    /// carried forward to the next cycle.
227    ResidualFilesFound {
228        /// Repo-relative paths of remaining dirty files.
229        files: Vec<String>,
230        /// Which pass just completed (1 = first pass, 2 = second pass).
231        pass: u8,
232    },
233
234    /// No residual uncommitted files detected after a commit pass.
235    ///
236    /// Pipeline may proceed normally; working tree is clean.
237    ResidualFilesNone,
238}