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 forge = self.forge.as_ref().ok_or_else(|| {
219 AgentError::PlanningFailed("Forge SDK not available for planning".to_string())
220 })?;
221
222 let planner_instance = planner::Planner::new(forge.clone());
224
225 let obs = observe::Observation {
227 query: constrained.observation.query.clone(),
228 symbols: vec![],
229 };
230
231 let steps = planner_instance.generate_steps(&obs).await?;
233
234 let impact = planner_instance.estimate_impact(&steps).await?;
236
237 let conflicts = planner_instance.detect_conflicts(&steps)?;
239
240 if !conflicts.is_empty() {
241 return Err(AgentError::PlanningFailed(format!(
242 "Found {} conflicts in plan",
243 conflicts.len()
244 )));
245 }
246
247 let mut ordered_steps = steps;
249 planner_instance.order_steps(&mut ordered_steps)?;
250
251 let rollback = planner_instance.generate_rollback(&ordered_steps);
253
254 Ok(ExecutionPlan {
255 steps: ordered_steps,
256 estimated_impact: planner::ImpactEstimate {
257 affected_files: impact.affected_files,
258 complexity: impact.complexity,
259 },
260 rollback_plan: rollback,
261 })
262 }
263
264 pub async fn mutate(&self, plan: ExecutionPlan) -> Result<MutationResult> {
266 let forge = self
267 .forge
268 .as_ref()
269 .ok_or_else(|| AgentError::MutationFailed("Forge SDK not available".to_string()))?;
270
271 let mut mutator = mutate::Mutator::new(forge.clone());
272 mutator.begin_transaction().await?;
273
274 for step in &plan.steps {
275 mutator.apply_step(step).await?;
276 }
277
278 Ok(MutationResult {
279 modified_files: vec![],
280 diffs: vec!["Transaction completed".to_string()],
281 })
282 }
283
284 pub async fn verify(&self, _result: MutationResult) -> Result<VerificationResult> {
286 let forge = self
287 .forge
288 .as_ref()
289 .ok_or_else(|| AgentError::VerificationFailed("Forge SDK not available".to_string()))?;
290
291 let verifier = verify::Verifier::new(forge.clone());
292 let report = verifier.verify(&self.codebase_path).await?;
293
294 Ok(VerificationResult {
295 passed: report.passed,
296 diagnostics: report
297 .diagnostics
298 .iter()
299 .map(|d| d.message.clone())
300 .collect(),
301 })
302 }
303
304 pub async fn commit(&self, result: VerificationResult) -> Result<CommitResult> {
306 let forge = self
307 .forge
308 .as_ref()
309 .ok_or_else(|| AgentError::CommitFailed("Forge SDK not available".to_string()))?;
310
311 let committer = commit::Committer::new(forge.clone());
312 let files: Vec<std::path::PathBuf> = result
313 .diagnostics
314 .iter()
315 .filter_map(|d| {
316 d.split(':')
319 .next()
320 .map(|s| std::path::PathBuf::from(s.trim()))
321 })
322 .collect();
323
324 let commit_report = committer.finalize(&self.codebase_path, &files).await?;
325
326 Ok(CommitResult {
327 transaction_id: commit_report.transaction_id,
328 files_committed: commit_report.files_committed,
329 })
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[tokio::test]
338 async fn test_agent_creation() {
339 let temp = tempfile::tempdir().unwrap();
340 let agent = Agent::new(temp.path()).await.unwrap();
341
342 assert_eq!(agent.codebase_path, temp.path());
343 }
344}