1use crate::{AgentError, Result};
7use tokio::fs;
8
9#[derive(Clone, Default)]
13pub struct Mutator {
14 transaction: Option<Transaction>,
16}
17
18#[derive(Clone, Debug)]
20struct Transaction {
21 applied_steps: Vec<String>,
23 rollback_state: Vec<RollbackState>,
25}
26
27#[derive(Clone, Debug)]
29struct RollbackState {
30 file: String,
32 original_content: String,
34}
35
36impl Mutator {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub async fn begin_transaction(&mut self) -> Result<()> {
44 if self.transaction.is_some() {
45 return Err(AgentError::MutationFailed(
46 "Transaction already in progress".to_string(),
47 ));
48 }
49
50 self.transaction = Some(Transaction {
51 applied_steps: Vec::new(),
52 rollback_state: Vec::new(),
53 });
54
55 Ok(())
56 }
57
58 pub async fn apply_step(&mut self, step: &crate::planner::PlanStep) -> Result<()> {
60 let transaction = self
61 .transaction
62 .as_mut()
63 .ok_or_else(|| AgentError::MutationFailed("No active transaction".to_string()))?;
64
65 match &step.operation {
66 crate::planner::PlanOperation::Rename { old, new } => {
67 transaction
69 .applied_steps
70 .push(format!("Rename {} to {}", old, new));
71 }
72 crate::planner::PlanOperation::Delete { name } => {
73 transaction.applied_steps.push(format!("Delete {}", name));
74 }
75 crate::planner::PlanOperation::Create { path, content } => {
76 if let Ok(original_content) = fs::read_to_string(path).await {
78 transaction.rollback_state.push(RollbackState {
79 file: path.clone(),
80 original_content,
81 });
82 }
83
84 fs::write(path, content).await.map_err(|e| {
86 AgentError::MutationFailed(format!("Failed to write {}: {}", path, e))
87 })?;
88
89 transaction.applied_steps.push(format!("Create {}", path));
90 }
91 crate::planner::PlanOperation::Inspect { .. } => {
92 }
94 crate::planner::PlanOperation::Modify { file, .. } => {
95 if let Ok(original_content) = std::fs::read_to_string(file) {
96 transaction.rollback_state.push(RollbackState {
97 file: file.clone(),
98 original_content,
99 });
100 }
101 transaction.applied_steps.push(format!("Modify {}", file));
102 }
103 }
104
105 Ok(())
106 }
107
108 pub async fn rollback(&mut self) -> Result<()> {
110 let transaction = self
111 .transaction
112 .take()
113 .ok_or_else(|| AgentError::MutationFailed("No active transaction".to_string()))?;
114
115 for state in transaction.rollback_state.iter().rev() {
117 std::fs::write(&state.file, &state.original_content).map_err(|e| {
118 AgentError::MutationFailed(format!("Rollback failed for {}: {}", state.file, e))
119 })?;
120 }
121
122 Ok(())
123 }
124
125 pub async fn preview(&self, steps: &[crate::planner::PlanStep]) -> Result<Vec<String>> {
127 let mut previews = Vec::new();
128
129 for step in steps {
130 match &step.operation {
131 crate::planner::PlanOperation::Create { path, content } => {
132 previews.push(format!("Create {}:\n{}", path, content));
133 }
134 crate::planner::PlanOperation::Delete { name } => {
135 previews.push(format!("Delete {}", name));
136 }
137 crate::planner::PlanOperation::Rename { old, new } => {
138 previews.push(format!("Rename {} to {}", old, new));
139 }
140 _ => {}
141 }
142 }
143
144 Ok(previews)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[tokio::test]
153 async fn test_mutator_creation() {
154 let mutator = Mutator::new();
155
156 assert!(mutator.transaction.is_none());
157 }
158
159 #[tokio::test]
160 async fn test_begin_transaction() {
161 let mut mutator = Mutator::new();
162
163 mutator.begin_transaction().await.unwrap();
164 assert!(mutator.transaction.is_some());
165
166 assert!(mutator.begin_transaction().await.is_err());
168 }
169
170 #[tokio::test]
171 async fn test_rollback() {
172 let mut mutator = Mutator::new();
173
174 mutator.begin_transaction().await.unwrap();
175 assert!(mutator.transaction.is_some());
176
177 mutator.rollback().await.unwrap();
178 assert!(mutator.transaction.is_none());
179 }
180}