1use std::path::PathBuf;
17
18pub mod observe;
20
21pub mod policy;
23
24pub mod planner;
26
27pub mod mutate;
29
30pub mod verify;
32
33pub mod commit;
35
36#[derive(thiserror::Error, Debug)]
38pub enum AgentError {
39 #[error("Observation failed: {0}")]
41 ObservationFailed(String),
42
43 #[error("Planning failed: {0}")]
45 PlanningFailed(String),
46
47 #[error("Mutation failed: {0}")]
49 MutationFailed(String),
50
51 #[error("Verification failed: {0}")]
53 VerificationFailed(String),
54
55 #[error("Commit failed: {0}")]
57 CommitFailed(String),
58
59 #[error("Policy violation: {0}")]
61 PolicyViolation(String),
62
63 #[error("Forge error: {0}")]
65 ForgeError(#[from] forge_core::ForgeError),
66}
67
68pub type Result<T> = std::result::Result<T, AgentError>;
70
71pub use policy::{Policy, PolicyReport, PolicyValidator, PolicyViolation};
73
74pub use observe::Observation;
76
77#[derive(Clone, Debug)]
79pub struct ConstrainedPlan {
80 pub observation: Observation,
82 pub policy_violations: Vec<policy::PolicyViolation>,
84}
85
86#[derive(Clone, Debug)]
88pub struct ExecutionPlan {
89 pub steps: Vec<planner::PlanStep>,
91 pub estimated_impact: planner::ImpactEstimate,
93 pub rollback_plan: Vec<planner::RollbackStep>,
95}
96
97#[derive(Clone, Debug)]
99pub struct MutationResult {
100 pub modified_files: Vec<PathBuf>,
102 pub diffs: Vec<String>,
104}
105
106#[derive(Clone, Debug)]
108pub struct VerificationResult {
109 pub passed: bool,
111 pub diagnostics: Vec<String>,
113}
114
115#[derive(Clone, Debug)]
117pub struct CommitResult {
118 pub transaction_id: String,
120 pub files_committed: Vec<PathBuf>,
122}
123
124pub struct Agent {
135 #[allow(dead_code)]
137 codebase_path: PathBuf,
138 forge: Option<forge_core::Forge>,
140}
141
142impl Agent {
143 pub async fn new(codebase_path: impl AsRef<std::path::Path>) -> Result<Self> {
149 let path = codebase_path.as_ref().to_path_buf();
150
151 let forge = forge_core::Forge::open(&path).await.ok();
153
154 Ok(Self {
155 codebase_path: path,
156 forge,
157 })
158 }
159
160 pub async fn observe(&self, query: &str) -> Result<Observation> {
166 let forge = self
167 .forge
168 .as_ref()
169 .ok_or_else(|| AgentError::ObservationFailed("Forge SDK not available".to_string()))?;
170
171 let observer = observe::Observer::new(forge.clone());
172 let obs = observer.gather(query).await?;
173
174 Ok(obs)
176 }
177
178 pub async fn constrain(
185 &self,
186 observation: Observation,
187 policies: Vec<policy::Policy>,
188 ) -> Result<ConstrainedPlan> {
189 let forge = self.forge.as_ref().ok_or_else(|| {
190 AgentError::ObservationFailed(
191 "Forge SDK not available for policy validation".to_string(),
192 )
193 })?;
194
195 let validator = policy::PolicyValidator::new(forge.clone());
197
198 let diff = policy::Diff {
201 file_path: std::path::PathBuf::from(""),
202 original: String::new(),
203 modified: String::new(),
204 changes: Vec::new(),
205 };
206
207 let report = validator.validate(&diff, &policies).await?;
209
210 Ok(ConstrainedPlan {
211 observation,
212 policy_violations: report.violations,
213 })
214 }
215
216 pub async fn plan(&self, constrained: ConstrainedPlan) -> Result<ExecutionPlan> {
218 let planner_instance = planner::Planner::new();
220
221 let obs = observe::Observation {
223 query: constrained.observation.query.clone(),
224 symbols: vec![],
225 };
226
227 let steps = planner_instance.generate_steps(&obs).await?;
229
230 let impact = planner_instance.estimate_impact(&steps).await?;
232
233 let conflicts = planner_instance.detect_conflicts(&steps)?;
235
236 if !conflicts.is_empty() {
237 return Err(AgentError::PlanningFailed(format!(
238 "Found {} conflicts in plan",
239 conflicts.len()
240 )));
241 }
242
243 let mut ordered_steps = steps;
245 planner_instance.order_steps(&mut ordered_steps)?;
246
247 let rollback = planner_instance.generate_rollback(&ordered_steps);
249
250 Ok(ExecutionPlan {
251 steps: ordered_steps,
252 estimated_impact: planner::ImpactEstimate {
253 affected_files: impact.affected_files,
254 complexity: impact.complexity,
255 },
256 rollback_plan: rollback,
257 })
258 }
259
260 pub async fn mutate(&self, plan: ExecutionPlan) -> Result<MutationResult> {
262 self.forge.as_ref().ok_or_else(|| AgentError::MutationFailed("Forge SDK not available".to_string()))?;
264
265 let mut mutator = mutate::Mutator::new();
266 mutator.begin_transaction().await?;
267
268 for step in &plan.steps {
269 mutator.apply_step(step).await?;
270 }
271
272 Ok(MutationResult {
273 modified_files: vec![],
274 diffs: vec!["Transaction completed".to_string()],
275 })
276 }
277
278 pub async fn verify(&self, _result: MutationResult) -> Result<VerificationResult> {
280 let verifier = verify::Verifier::new();
281 let report = verifier.verify(&self.codebase_path).await?;
282
283 Ok(VerificationResult {
284 passed: report.passed,
285 diagnostics: report
286 .diagnostics
287 .iter()
288 .map(|d| d.message.clone())
289 .collect(),
290 })
291 }
292
293 pub async fn commit(&self, result: VerificationResult) -> Result<CommitResult> {
295 let committer = commit::Committer::new();
296 let files: Vec<std::path::PathBuf> = result
297 .diagnostics
298 .iter()
299 .filter_map(|d| {
300 d.split(':')
303 .next()
304 .map(|s| std::path::PathBuf::from(s.trim()))
305 })
306 .collect();
307
308 let commit_report = committer.finalize(&self.codebase_path, &files).await?;
309
310 Ok(CommitResult {
311 transaction_id: commit_report.transaction_id,
312 files_committed: commit_report.files_committed,
313 })
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[tokio::test]
322 async fn test_agent_creation() {
323 let temp = tempfile::tempdir().unwrap();
324 let agent = Agent::new(temp.path()).await.unwrap();
325
326 assert_eq!(agent.codebase_path, temp.path());
327 }
328}