1use crate::{
10 audit::AuditEvent, audit::AuditLog, policy::PolicyValidator, CommitResult, ConstrainedPlan,
11 ExecutionPlan, MutationResult, Observation, VerificationResult,
12};
13use chrono::Utc;
14use forge_core::Forge;
15use std::sync::Arc;
16
17#[derive(Clone, Debug, PartialEq)]
19pub enum AgentPhase {
20 Observe,
22 Constrain,
24 Plan,
26 Mutate,
28 Verify,
30 Commit,
32}
33
34#[derive(Clone, Debug)]
36pub struct LoopResult {
37 pub transaction_id: String,
39 pub modified_files: Vec<std::path::PathBuf>,
41 pub audit_events: Vec<AuditEvent>,
43}
44
45pub struct AgentLoop {
51 forge: Arc<Forge>,
53 transaction: Option<crate::transaction::Transaction>,
55 audit_log: AuditLog,
57}
58
59impl AgentLoop {
60 pub fn new(forge: Arc<Forge>) -> Self {
66 Self {
67 forge,
68 transaction: None,
69 audit_log: AuditLog::new(),
70 }
71 }
72
73 pub async fn run(&mut self, query: &str) -> Result<LoopResult, crate::AgentError> {
83 let observation = match self.observe_phase(query).await {
85 Ok(obs) => obs,
86 Err(e) => {
87 self.record_rollback(&e).await;
88 return Err(e);
89 }
90 };
91
92 let constrained = match self.constrain_phase(observation).await {
94 Ok(constrained) => constrained,
95 Err(e) => {
96 self.record_rollback(&e).await;
97 return Err(e);
98 }
99 };
100
101 let plan = match self.plan_phase(constrained).await {
103 Ok(plan) => plan,
104 Err(e) => {
105 self.record_rollback(&e).await;
106 return Err(e);
107 }
108 };
109
110 let mutation_result = match self.mutate_phase(plan).await {
112 Ok(result) => result,
113 Err(e) => {
114 self.record_rollback(&e).await;
115 return Err(e);
116 }
117 };
118
119 let verification = match self.verify_phase(mutation_result).await {
121 Ok(verification) => verification,
122 Err(e) => {
123 self.record_rollback(&e).await;
124 return Err(e);
125 }
126 };
127
128 let commit_result = match self.commit_phase(verification).await {
130 Ok(result) => result,
131 Err(e) => {
132 self.record_rollback(&e).await;
133 return Err(e);
134 }
135 };
136
137 Ok(LoopResult {
139 transaction_id: commit_result.transaction_id,
140 modified_files: commit_result.files_committed,
141 audit_events: self.audit_log.clone().into_events(),
142 })
143 }
144
145 async fn observe_phase(&mut self, query: &str) -> Result<Observation, crate::AgentError> {
147 let observer = crate::observe::Observer::new((*self.forge).clone());
149 let observation = observer
150 .gather(query)
151 .await
152 .map_err(|e| crate::AgentError::ObservationFailed(e.to_string()))?;
153
154 let symbol_count = observation.symbols.len();
155
156 self.audit_log
158 .record(AuditEvent::Observe {
159 timestamp: Utc::now(),
160 query: query.to_string(),
161 symbol_count,
162 })
163 .await
164 .map_err(|e| crate::AgentError::ObservationFailed(e.to_string()))?;
165
166 Ok(observation)
167 }
168
169 async fn constrain_phase(
171 &mut self,
172 observation: Observation,
173 ) -> Result<ConstrainedPlan, crate::AgentError> {
174 let validator = PolicyValidator::new((*self.forge).clone());
176 let diff = crate::policy::Diff {
177 file_path: std::path::PathBuf::from(""),
178 original: String::new(),
179 modified: String::new(),
180 changes: Vec::new(),
181 };
182 let policies = Vec::new(); let report = validator
185 .validate(&diff, &policies)
186 .await
187 .map_err(|e| crate::AgentError::PolicyViolation(e.to_string()))?;
188
189 let policy_count = policies.len();
190 let violations = report.violations.len();
191
192 self.audit_log
194 .record(AuditEvent::Constrain {
195 timestamp: Utc::now(),
196 policy_count,
197 violations,
198 })
199 .await
200 .map_err(|e| crate::AgentError::PolicyViolation(e.to_string()))?;
201
202 Ok(ConstrainedPlan {
203 observation,
204 policy_violations: report.violations,
205 })
206 }
207
208 async fn plan_phase(
210 &mut self,
211 constrained: ConstrainedPlan,
212 ) -> Result<ExecutionPlan, crate::AgentError> {
213 let planner = crate::planner::Planner::new();
215
216 let steps = planner
218 .generate_steps(&constrained.observation)
219 .await
220 .map_err(|e| crate::AgentError::PlanningFailed(e.to_string()))?;
221
222 let impact = planner
224 .estimate_impact(&steps)
225 .await
226 .map_err(|e| crate::AgentError::PlanningFailed(e.to_string()))?;
227 let estimated_files = impact.affected_files.len();
228
229 let conflicts = planner
231 .detect_conflicts(&steps)
232 .map_err(|e| crate::AgentError::PlanningFailed(e.to_string()))?;
233
234 if !conflicts.is_empty() {
235 return Err(crate::AgentError::PlanningFailed(format!(
236 "Found {} conflicts in plan",
237 conflicts.len()
238 )));
239 }
240
241 let mut ordered_steps = steps;
243 planner
244 .order_steps(&mut ordered_steps)
245 .map_err(|e| crate::AgentError::PlanningFailed(e.to_string()))?;
246
247 let step_count = ordered_steps.len();
248
249 let rollback = planner.generate_rollback(&ordered_steps);
251
252 self.audit_log
254 .record(AuditEvent::Plan {
255 timestamp: Utc::now(),
256 step_count,
257 estimated_files,
258 })
259 .await
260 .map_err(|e| crate::AgentError::PlanningFailed(e.to_string()))?;
261
262 Ok(ExecutionPlan {
263 steps: ordered_steps,
264 estimated_impact: impact,
265 rollback_plan: rollback,
266 })
267 }
268
269 async fn mutate_phase(
271 &mut self,
272 plan: ExecutionPlan,
273 ) -> Result<MutationResult, crate::AgentError> {
274 let mut mutator = crate::mutate::Mutator::new();
276 mutator
277 .begin_transaction()
278 .await
279 .map_err(|e| crate::AgentError::MutationFailed(e.to_string()))?;
280
281 for step in &plan.steps {
283 mutator
284 .apply_step(step)
285 .await
286 .map_err(|e| crate::AgentError::MutationFailed(e.to_string()))?;
287 }
288
289 self.transaction = Some(mutator.into_transaction()?);
291
292 let files_modified: Vec<String> = Vec::new();
294
295 self.audit_log
297 .record(AuditEvent::Mutate {
298 timestamp: Utc::now(),
299 files_modified,
300 })
301 .await
302 .map_err(|e| crate::AgentError::MutationFailed(e.to_string()))?;
303
304 Ok(MutationResult {
305 modified_files: Vec::new(), diffs: vec!["Mutation applied".to_string()],
307 })
308 }
309
310 async fn verify_phase(
312 &mut self,
313 _result: MutationResult,
314 ) -> Result<VerificationResult, crate::AgentError> {
315 let verifier = crate::verify::Verifier::new();
317
318 let report = verifier
320 .verify(std::path::Path::new(""))
321 .await
322 .map_err(|e| crate::AgentError::VerificationFailed(e.to_string()))?;
323
324 let diagnostic_count = report.diagnostics.len();
325 let passed = report.passed;
326
327 self.audit_log
329 .record(AuditEvent::Verify {
330 timestamp: Utc::now(),
331 passed,
332 diagnostic_count,
333 })
334 .await
335 .map_err(|e| crate::AgentError::VerificationFailed(e.to_string()))?;
336
337 Ok(VerificationResult {
338 passed: report.passed,
339 diagnostics: report
340 .diagnostics
341 .iter()
342 .map(|d| d.message.clone())
343 .collect(),
344 })
345 }
346
347 async fn commit_phase(
349 &mut self,
350 verification: VerificationResult,
351 ) -> Result<CommitResult, crate::AgentError> {
352 let files: Vec<std::path::PathBuf> = verification
354 .diagnostics
355 .iter()
356 .filter_map(|d| {
357 d.split(':')
358 .next()
359 .map(|s| std::path::PathBuf::from(s.trim()))
360 })
361 .collect();
362
363 let committer = crate::commit::Committer::new();
365 let commit_report = committer
366 .finalize(std::path::Path::new(""), &files)
367 .await
368 .map_err(|e| crate::AgentError::CommitFailed(e.to_string()))?;
369
370 if let Some(txn) = self.transaction.take() {
372 txn.commit()
373 .await
374 .map_err(|e| crate::AgentError::CommitFailed(e.to_string()))?;
375 }
376
377 let transaction_id = commit_report.transaction_id.clone();
378
379 self.audit_log
381 .record(AuditEvent::Commit {
382 timestamp: Utc::now(),
383 transaction_id,
384 })
385 .await
386 .map_err(|e| crate::AgentError::CommitFailed(e.to_string()))?;
387
388 Ok(CommitResult {
389 transaction_id: commit_report.transaction_id,
390 files_committed: commit_report.files_committed,
391 })
392 }
393
394 async fn record_rollback(&mut self, error: &crate::AgentError) {
396 if let Some(txn) = self.transaction.take() {
398 let _ = txn.rollback().await;
399 }
400
401 let phase = match error {
403 crate::AgentError::ObservationFailed(_) => "Observe",
404 crate::AgentError::PolicyViolation(_) => "Constrain",
405 crate::AgentError::PlanningFailed(_) => "Plan",
406 crate::AgentError::MutationFailed(_) => "Mutate",
407 crate::AgentError::VerificationFailed(_) => "Verify",
408 crate::AgentError::CommitFailed(_) => "Commit",
409 crate::AgentError::ForgeError(_) => "Forge",
410 };
411
412 let _ = self
414 .audit_log
415 .record(AuditEvent::Rollback {
416 timestamp: Utc::now(),
417 reason: error.to_string(),
418 phase: phase.to_string(),
419 })
420 .await;
421 }
422
423 #[cfg(test)]
425 pub fn audit_log(&self) -> &AuditLog {
426 &self.audit_log
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use tempfile::TempDir;
434
435 async fn create_test_loop() -> (AgentLoop, TempDir) {
436 let temp_dir = TempDir::new().unwrap();
437 let forge = Forge::open(temp_dir.path()).await.unwrap();
438 let agent_loop = AgentLoop::new(Arc::new(forge));
439 (agent_loop, temp_dir)
440 }
441
442 #[tokio::test]
443 async fn test_agent_loop_creation() {
444 let temp_dir = TempDir::new().unwrap();
445 let forge = Forge::open(temp_dir.path()).await.unwrap();
446 let agent_loop = AgentLoop::new(Arc::new(forge));
447
448 assert!(agent_loop.transaction.is_none());
450 assert_eq!(agent_loop.audit_log().len(), 0);
451 }
452
453 #[tokio::test]
454 async fn test_agent_loop_successful_run() {
455 let temp_dir = TempDir::new().unwrap();
456 let forge = Forge::open(temp_dir.path()).await.unwrap();
457 let mut agent_loop = AgentLoop::new(Arc::new(forge));
458
459 let result = agent_loop.run("test query").await;
460
461 match result {
464 Ok(loop_result) => {
465 assert!(!loop_result.transaction_id.is_empty());
467 assert_eq!(loop_result.audit_events.len(), 6);
469 }
470 Err(e) => {
471 assert!(e.to_string().contains("Verification") || e.to_string().contains("verification"));
473 }
474 }
475 }
476
477 #[tokio::test]
478 async fn test_agent_loop_state_isolation() {
479 let temp_dir = TempDir::new().unwrap();
480 let forge = Forge::open(temp_dir.path()).await.unwrap();
481 let mut agent_loop = AgentLoop::new(Arc::new(forge));
482
483 let result1 = agent_loop.run("first query").await;
485 let events1_count = match &result1 {
486 Ok(r) => r.audit_events.len(),
487 Err(_) => {
488 agent_loop.audit_log().len()
491 }
492 };
493
494 let result2 = agent_loop.run("second query").await;
496 let events2_count = match &result2 {
497 Ok(r) => r.audit_events.len(),
498 Err(_) => agent_loop.audit_log().len(),
499 };
500
501 assert!(events1_count > 0);
504 assert!(events2_count > 0);
505 }
506
507 #[tokio::test]
508 async fn test_phase_transitions_recorded() {
509 let temp_dir = TempDir::new().unwrap();
510 let forge = Forge::open(temp_dir.path()).await.unwrap();
511 let mut agent_loop = AgentLoop::new(Arc::new(forge));
512
513 let result = agent_loop.run("test query").await;
514
515 let events = match result {
517 Ok(r) => r.audit_events,
518 Err(_) => agent_loop.audit_log().clone().into_events(),
519 };
520
521 assert!(events.len() >= 5);
524
525 assert!(matches!(events[0], AuditEvent::Observe { .. }));
527 assert!(matches!(events[1], AuditEvent::Constrain { .. }));
528 assert!(matches!(events[2], AuditEvent::Plan { .. }));
529 assert!(matches!(events[3], AuditEvent::Mutate { .. }));
530
531 let is_valid_fifth = matches!(events[4], AuditEvent::Verify { .. } | AuditEvent::Rollback { .. });
534 assert!(is_valid_fifth, "Expected Verify or Rollback at index 4, got: {:?}", events[4]);
535 }
536
537 #[tokio::test]
538 async fn test_agent_loop_returns_loop_result() {
539 let temp_dir = TempDir::new().unwrap();
540 let forge = Forge::open(temp_dir.path()).await.unwrap();
541 let mut agent_loop = AgentLoop::new(Arc::new(forge));
542
543 let result = agent_loop.run("test query").await;
544
545 match result {
547 Ok(loop_result) => {
548 assert!(!loop_result.transaction_id.is_empty());
549 assert!(loop_result.modified_files.is_empty()); assert!(!loop_result.audit_events.is_empty());
551 }
552 Err(e) => {
553 assert!(e.to_string().contains("Verification") || e.to_string().contains("verification"));
555 }
556 }
557 }
558}