ricecoder_generation/
generation_plan_builder.rs

1//! Generation plan builder for creating and validating generation plans
2//!
3//! Builds generation plans from spec requirements, determines task dependencies,
4//! orders steps for correct execution, and validates plan completeness.
5
6use crate::error::GenerationError;
7use crate::spec_processor::{ConstraintType, GenerationPlan, GenerationStep};
8use ricecoder_specs::models::{Priority, Requirement};
9use std::collections::{HashMap, HashSet};
10
11/// Builds generation plans from specifications
12#[derive(Debug, Clone)]
13pub struct GenerationPlanBuilder {
14    /// Maximum number of steps allowed in a plan
15    max_steps: usize,
16}
17
18/// Validation result for a generation plan
19#[derive(Debug, Clone)]
20pub struct PlanValidation {
21    /// Whether the plan is valid
22    pub is_valid: bool,
23    /// Validation errors
24    pub errors: Vec<String>,
25    /// Validation warnings
26    pub warnings: Vec<String>,
27}
28
29impl GenerationPlanBuilder {
30    /// Creates a new GenerationPlanBuilder
31    pub fn new() -> Self {
32        Self { max_steps: 1000 }
33    }
34
35    /// Sets the maximum number of steps allowed
36    pub fn with_max_steps(mut self, max_steps: usize) -> Self {
37        self.max_steps = max_steps;
38        self
39    }
40
41    /// Creates generation steps from spec requirements
42    ///
43    /// # Arguments
44    ///
45    /// * `requirements` - The requirements to convert to steps
46    ///
47    /// # Returns
48    ///
49    /// A vector of generation steps
50    pub fn create_steps(
51        &self,
52        requirements: &[Requirement],
53    ) -> Result<Vec<GenerationStep>, GenerationError> {
54        if requirements.len() > self.max_steps {
55            return Err(GenerationError::SpecError(format!(
56                "Too many requirements: {} exceeds maximum of {}",
57                requirements.len(),
58                self.max_steps
59            )));
60        }
61
62        let mut steps = Vec::new();
63
64        for (idx, requirement) in requirements.iter().enumerate() {
65            let step = GenerationStep {
66                id: format!("step-{}", requirement.id),
67                description: requirement.user_story.clone(),
68                requirement_ids: vec![requirement.id.clone()],
69                acceptance_criteria: requirement.acceptance_criteria.clone(),
70                priority: requirement.priority,
71                optional: requirement.priority == Priority::Could,
72                sequence: idx,
73            };
74            steps.push(step);
75        }
76
77        Ok(steps)
78    }
79
80    /// Determines task dependencies between steps
81    ///
82    /// # Arguments
83    ///
84    /// * `steps` - The generation steps
85    ///
86    /// # Returns
87    ///
88    /// A vector of dependencies as (from_step_id, to_step_id) tuples
89    pub fn determine_dependencies(
90        &self,
91        steps: &[GenerationStep],
92    ) -> Result<Vec<(String, String)>, GenerationError> {
93        let mut dependencies = Vec::new();
94
95        // Create a map of step IDs to their sequence numbers
96        let mut step_sequence: HashMap<String, usize> = HashMap::new();
97        for step in steps {
98            step_sequence.insert(step.id.clone(), step.sequence);
99        }
100
101        // For now, enforce sequential ordering
102        // Steps are executed in order of their sequence number
103        for i in 0..steps.len().saturating_sub(1) {
104            let current_step = &steps[i];
105            let next_step = &steps[i + 1];
106
107            // Only add dependency if next step has higher sequence
108            if next_step.sequence > current_step.sequence {
109                dependencies.push((current_step.id.clone(), next_step.id.clone()));
110            }
111        }
112
113        Ok(dependencies)
114    }
115
116    /// Orders steps for correct execution
117    ///
118    /// # Arguments
119    ///
120    /// * `steps` - The generation steps to order
121    /// * `dependencies` - The dependencies between steps
122    ///
123    /// # Returns
124    ///
125    /// An ordered vector of steps
126    pub fn order_steps(
127        &self,
128        mut steps: Vec<GenerationStep>,
129        dependencies: &[(String, String)],
130    ) -> Result<Vec<GenerationStep>, GenerationError> {
131        // Build dependency graph
132        let mut dep_graph: HashMap<String, Vec<String>> = HashMap::new();
133        let mut in_degree: HashMap<String, usize> = HashMap::new();
134
135        // Initialize all steps in the graph
136        for step in &steps {
137            dep_graph.insert(step.id.clone(), Vec::new());
138            in_degree.insert(step.id.clone(), 0);
139        }
140
141        // Add edges
142        for (from, to) in dependencies {
143            if let Some(deps) = dep_graph.get_mut(from) {
144                deps.push(to.clone());
145            }
146            *in_degree.get_mut(to).unwrap_or(&mut 0) += 1;
147        }
148
149        // Topological sort using Kahn's algorithm
150        let mut queue: Vec<String> = in_degree
151            .iter()
152            .filter(|(_, &degree)| degree == 0)
153            .map(|(id, _)| id.clone())
154            .collect();
155
156        let mut ordered = Vec::new();
157
158        while !queue.is_empty() {
159            queue.sort(); // For deterministic ordering
160            let current = queue.remove(0);
161
162            // Find the step with this ID
163            if let Some(pos) = steps.iter().position(|s| s.id == current) {
164                ordered.push(steps.remove(pos));
165            }
166
167            // Process neighbors
168            if let Some(neighbors) = dep_graph.get(&current) {
169                for neighbor in neighbors.clone() {
170                    let degree = in_degree.get_mut(&neighbor).unwrap();
171                    *degree -= 1;
172                    if *degree == 0 {
173                        queue.push(neighbor);
174                    }
175                }
176            }
177        }
178
179        if !steps.is_empty() {
180            return Err(GenerationError::SpecError(
181                "Circular dependency detected in generation steps".to_string(),
182            ));
183        }
184
185        Ok(ordered)
186    }
187
188    /// Validates plan completeness
189    ///
190    /// # Arguments
191    ///
192    /// * `plan` - The generation plan to validate
193    ///
194    /// # Returns
195    ///
196    /// A validation result
197    pub fn validate_plan(&self, plan: &GenerationPlan) -> PlanValidation {
198        let mut errors = Vec::new();
199        let mut warnings = Vec::new();
200
201        // Check that plan has steps
202        if plan.steps.is_empty() {
203            errors.push("Generation plan has no steps".to_string());
204        }
205
206        // Check that all steps have unique IDs
207        let mut seen_ids = HashSet::new();
208        for step in &plan.steps {
209            if !seen_ids.insert(&step.id) {
210                errors.push(format!("Duplicate step ID: {}", step.id));
211            }
212        }
213
214        // Check that all dependencies reference existing steps
215        let step_ids: HashSet<_> = plan.steps.iter().map(|s| &s.id).collect();
216        for (from, to) in &plan.dependencies {
217            if !step_ids.contains(from) {
218                errors.push(format!("Dependency references non-existent step: {}", from));
219            }
220            if !step_ids.contains(to) {
221                errors.push(format!("Dependency references non-existent step: {}", to));
222            }
223        }
224
225        // Check that all steps have acceptance criteria
226        for step in &plan.steps {
227            if step.acceptance_criteria.is_empty() && !step.optional {
228                warnings.push(format!("Step {} has no acceptance criteria", step.id));
229            }
230        }
231
232        // Check that constraints are present for code quality requirements
233        let has_quality_constraints = plan
234            .constraints
235            .iter()
236            .any(|c| c.constraint_type == ConstraintType::CodeQuality);
237
238        if !plan.steps.is_empty() && !has_quality_constraints {
239            warnings.push("No code quality constraints found in plan".to_string());
240        }
241
242        let is_valid = errors.is_empty();
243
244        PlanValidation {
245            is_valid,
246            errors,
247            warnings,
248        }
249    }
250}
251
252impl Default for GenerationPlanBuilder {
253    fn default() -> Self {
254        Self::new()
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use ricecoder_specs::models::AcceptanceCriterion;
262
263    fn create_test_requirement(id: &str, priority: Priority) -> Requirement {
264        Requirement {
265            id: id.to_string(),
266            user_story: format!("User story for {}", id),
267            acceptance_criteria: vec![AcceptanceCriterion {
268                id: format!("{}-ac-1", id),
269                when: "condition".to_string(),
270                then: "outcome".to_string(),
271            }],
272            priority,
273        }
274    }
275
276    #[test]
277    fn test_create_steps_from_requirements() {
278        let builder = GenerationPlanBuilder::new();
279        let requirements = vec![
280            create_test_requirement("req-1", Priority::Must),
281            create_test_requirement("req-2", Priority::Should),
282        ];
283
284        let steps = builder
285            .create_steps(&requirements)
286            .expect("Failed to create steps");
287
288        assert_eq!(steps.len(), 2);
289        assert_eq!(steps[0].id, "step-req-1");
290        assert_eq!(steps[1].id, "step-req-2");
291        assert_eq!(steps[0].sequence, 0);
292        assert_eq!(steps[1].sequence, 1);
293    }
294
295    #[test]
296    fn test_create_steps_marks_optional() {
297        let builder = GenerationPlanBuilder::new();
298        let requirements = vec![
299            create_test_requirement("req-1", Priority::Must),
300            create_test_requirement("req-2", Priority::Could),
301        ];
302
303        let steps = builder
304            .create_steps(&requirements)
305            .expect("Failed to create steps");
306
307        assert!(!steps[0].optional);
308        assert!(steps[1].optional);
309    }
310
311    #[test]
312    fn test_determine_dependencies_sequential() {
313        let builder = GenerationPlanBuilder::new();
314        let steps = vec![
315            GenerationStep {
316                id: "step-1".to_string(),
317                description: "Step 1".to_string(),
318                requirement_ids: vec!["req-1".to_string()],
319                acceptance_criteria: vec![],
320                priority: Priority::Must,
321                optional: false,
322                sequence: 0,
323            },
324            GenerationStep {
325                id: "step-2".to_string(),
326                description: "Step 2".to_string(),
327                requirement_ids: vec!["req-2".to_string()],
328                acceptance_criteria: vec![],
329                priority: Priority::Must,
330                optional: false,
331                sequence: 1,
332            },
333        ];
334
335        let deps = builder
336            .determine_dependencies(&steps)
337            .expect("Failed to determine dependencies");
338
339        assert_eq!(deps.len(), 1);
340        assert_eq!(deps[0], ("step-1".to_string(), "step-2".to_string()));
341    }
342
343    #[test]
344    fn test_order_steps_topological_sort() {
345        let builder = GenerationPlanBuilder::new();
346        let steps = vec![
347            GenerationStep {
348                id: "step-1".to_string(),
349                description: "Step 1".to_string(),
350                requirement_ids: vec![],
351                acceptance_criteria: vec![],
352                priority: Priority::Must,
353                optional: false,
354                sequence: 0,
355            },
356            GenerationStep {
357                id: "step-2".to_string(),
358                description: "Step 2".to_string(),
359                requirement_ids: vec![],
360                acceptance_criteria: vec![],
361                priority: Priority::Must,
362                optional: false,
363                sequence: 1,
364            },
365        ];
366
367        let dependencies = vec![("step-1".to_string(), "step-2".to_string())];
368
369        let ordered = builder
370            .order_steps(steps, &dependencies)
371            .expect("Failed to order steps");
372
373        assert_eq!(ordered[0].id, "step-1");
374        assert_eq!(ordered[1].id, "step-2");
375    }
376
377    #[test]
378    fn test_validate_plan_empty_steps() {
379        let builder = GenerationPlanBuilder::new();
380        let plan = GenerationPlan {
381            id: "plan-1".to_string(),
382            spec_id: "spec-1".to_string(),
383            steps: vec![],
384            dependencies: vec![],
385            constraints: vec![],
386        };
387
388        let validation = builder.validate_plan(&plan);
389
390        assert!(!validation.is_valid);
391        assert!(validation.errors.iter().any(|e| e.contains("no steps")));
392    }
393
394    #[test]
395    fn test_validate_plan_duplicate_ids() {
396        let builder = GenerationPlanBuilder::new();
397        let plan = GenerationPlan {
398            id: "plan-1".to_string(),
399            spec_id: "spec-1".to_string(),
400            steps: vec![
401                GenerationStep {
402                    id: "step-1".to_string(),
403                    description: "Step 1".to_string(),
404                    requirement_ids: vec![],
405                    acceptance_criteria: vec![],
406                    priority: Priority::Must,
407                    optional: false,
408                    sequence: 0,
409                },
410                GenerationStep {
411                    id: "step-1".to_string(),
412                    description: "Step 1 duplicate".to_string(),
413                    requirement_ids: vec![],
414                    acceptance_criteria: vec![],
415                    priority: Priority::Must,
416                    optional: false,
417                    sequence: 1,
418                },
419            ],
420            dependencies: vec![],
421            constraints: vec![],
422        };
423
424        let validation = builder.validate_plan(&plan);
425
426        assert!(!validation.is_valid);
427        assert!(validation.errors.iter().any(|e| e.contains("Duplicate")));
428    }
429
430    #[test]
431    fn test_validate_plan_invalid_dependencies() {
432        let builder = GenerationPlanBuilder::new();
433        let plan = GenerationPlan {
434            id: "plan-1".to_string(),
435            spec_id: "spec-1".to_string(),
436            steps: vec![GenerationStep {
437                id: "step-1".to_string(),
438                description: "Step 1".to_string(),
439                requirement_ids: vec![],
440                acceptance_criteria: vec![],
441                priority: Priority::Must,
442                optional: false,
443                sequence: 0,
444            }],
445            dependencies: vec![("step-1".to_string(), "step-nonexistent".to_string())],
446            constraints: vec![],
447        };
448
449        let validation = builder.validate_plan(&plan);
450
451        assert!(!validation.is_valid);
452        assert!(validation.errors.iter().any(|e| e.contains("non-existent")));
453    }
454}