Skip to main content

forge_agent/
lib.rs

1//! ForgeKit agent layer - Deterministic AI loop.
2//!
3//! This crate provides a deterministic agent loop for AI-driven code operations:
4//!
5//! - Observation: Gather context from the graph
6//! - Constraint: Apply policy rules
7//! - Planning: Generate execution steps
8//! - Mutation: Apply changes
9//! - Verification: Validate results
10//! - Commit: Finalize transaction
11//!
12//! # Status
13//!
14//! This crate is under active development. Observation and planning phases are implemented.
15
16use std::path::PathBuf;
17
18// Observation module (Phase 4 - Task 1)
19pub mod observe;
20
21// Policy module (Phase 4 - Task 2)
22pub mod policy;
23
24// Planning module (Phase 4 - Task 3)
25pub mod planner;
26
27// Mutation module (Phase 4 - Task 4)
28pub mod mutate;
29
30// Verification module (Phase 4 - Task 5)
31pub mod verify;
32
33// Commit module (Phase 4 - Task 6)
34pub mod commit;
35
36/// Error types for agent operations.
37#[derive(thiserror::Error, Debug)]
38pub enum AgentError {
39    /// Observation phase failed
40    #[error("Observation failed: {0}")]
41    ObservationFailed(String),
42
43    /// Planning phase failed
44    #[error("Planning failed: {0}")]
45    PlanningFailed(String),
46
47    /// Mutation phase failed
48    #[error("Mutation failed: {0}")]
49    MutationFailed(String),
50
51    /// Verification phase failed
52    #[error("Verification failed: {0}")]
53    VerificationFailed(String),
54
55    /// Commit phase failed
56    #[error("Commit failed: {0}")]
57    CommitFailed(String),
58
59    /// Policy constraint violated
60    #[error("Policy violation: {0}")]
61    PolicyViolation(String),
62
63    /// Error from Forge SDK
64    #[error("Forge error: {0}")]
65    ForgeError(#[from] forge_core::ForgeError),
66}
67
68/// Result type for agent operations.
69pub type Result<T> = std::result::Result<T, AgentError>;
70
71// Re-export policy module
72pub use policy::{Policy, PolicyReport, PolicyValidator, PolicyViolation};
73
74// Re-export observation types
75pub use observe::Observation;
76
77/// Result of applying policy constraints.
78#[derive(Clone, Debug)]
79pub struct ConstrainedPlan {
80    /// The original observation
81    pub observation: Observation,
82    /// Any policy violations detected
83    pub policy_violations: Vec<policy::PolicyViolation>,
84}
85
86/// Execution plan for the mutation phase.
87#[derive(Clone, Debug)]
88pub struct ExecutionPlan {
89    /// Steps to execute
90    pub steps: Vec<planner::PlanStep>,
91    /// Estimated impact
92    pub estimated_impact: planner::ImpactEstimate,
93    /// Rollback plan
94    pub rollback_plan: Vec<planner::RollbackStep>,
95}
96
97/// Result of the mutation phase.
98#[derive(Clone, Debug)]
99pub struct MutationResult {
100    /// Files that were modified
101    pub modified_files: Vec<PathBuf>,
102    /// Diffs of changes made
103    pub diffs: Vec<String>,
104}
105
106/// Result of the verification phase.
107#[derive(Clone, Debug)]
108pub struct VerificationResult {
109    /// Whether verification passed
110    pub passed: bool,
111    /// Any diagnostics or errors
112    pub diagnostics: Vec<String>,
113}
114
115/// Result of the commit phase.
116#[derive(Clone, Debug)]
117pub struct CommitResult {
118    /// Transaction ID for the commit
119    pub transaction_id: String,
120    /// Files that were committed
121    pub files_committed: Vec<PathBuf>,
122}
123
124/// Agent for deterministic AI-driven code operations.
125///
126/// The agent follows a strict loop:
127/// 1. Observe: Gather context from the graph
128/// 2. Constrain: Apply policy rules
129/// 3. Plan: Generate execution steps
130/// 4. Mutate: Apply changes
131/// 5. Verify: Validate results
132/// 6. Commit: Finalize transaction
133///
134pub struct Agent {
135    /// Path to the codebase
136    #[allow(dead_code)]
137    codebase_path: PathBuf,
138    /// Forge SDK instance for graph queries
139    forge: Option<forge_core::Forge>,
140}
141
142impl Agent {
143    /// Creates a new agent for the given codebase.
144    ///
145    /// # Arguments
146    ///
147    /// * `codebase_path` - Path to the codebase
148    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        // Try to initialize Forge SDK
152        let forge = forge_core::Forge::open(&path).await.ok();
153
154        Ok(Self {
155            codebase_path: path,
156            forge,
157        })
158    }
159
160    /// Observes the codebase to gather context for a query.
161    ///
162    /// # Arguments
163    ///
164    /// * `query` - The natural language query or request
165    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        // Return the observation directly - it's already the correct type
175        Ok(obs)
176    }
177
178    /// Applies policy constraints to the observation.
179    ///
180    /// # Arguments
181    ///
182    /// * `observation` - The observation to constrain
183    /// * `policies` - The policies to validate
184    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        // Create a validator
196        let validator = policy::PolicyValidator::new(forge.clone());
197
198        // For observation, create a placeholder diff
199        // In production, this would be the actual planned diff
200        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        // Validate policies
208        let report = validator.validate(&diff, &policies).await?;
209
210        Ok(ConstrainedPlan {
211            observation,
212            policy_violations: report.violations,
213        })
214    }
215
216    /// Generates an execution plan from the constrained observation.
217    pub async fn plan(&self, constrained: ConstrainedPlan) -> Result<ExecutionPlan> {
218        // Create planner
219        let planner_instance = planner::Planner::new();
220
221        // Convert observation to the planner's format
222        let obs = observe::Observation {
223            query: constrained.observation.query.clone(),
224            symbols: vec![],
225        };
226
227        // Generate steps
228        let steps = planner_instance.generate_steps(&obs).await?;
229
230        // Estimate impact
231        let impact = planner_instance.estimate_impact(&steps).await?;
232
233        // Detect conflicts
234        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        // Order steps based on dependencies
244        let mut ordered_steps = steps;
245        planner_instance.order_steps(&mut ordered_steps)?;
246
247        // Generate rollback plan
248        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    /// Executes the mutation phase of the plan.
261    pub async fn mutate(&self, plan: ExecutionPlan) -> Result<MutationResult> {
262        // Verify forge is available
263        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    /// Verifies the mutation result.
279    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    /// Commits the verified mutation.
294    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                // Parse diagnostics to extract file paths
301                // Format: "file:line:col: message"
302                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}