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}