Skip to main content

depyler_tooling/hunt_mode/
planner.rs

1//! Hunt Planner - Failure Pattern Classification and Prioritization
2//!
3//! Implements Heijunka (平準化) - Level the Workload
4//! Processes errors in frequency order to ensure maximum impact per cycle.
5//!
6//! Uses the Pareto principle: 20% of patterns cause 80% of failures.
7
8use std::cmp::Ordering;
9use std::collections::{BinaryHeap, HashMap};
10
11/// A cluster of similar compilation errors
12#[derive(Debug, Clone)]
13pub struct ErrorCluster {
14    /// Unique identifier for this cluster
15    pub id: String,
16    /// Error code (e.g., "E0308", "E0432")
17    pub error_code: String,
18    /// Human-readable description
19    pub description: String,
20    /// Number of occurrences in the corpus
21    pub frequency: u32,
22    /// Estimated severity (1-10)
23    pub severity: u8,
24    /// Example error messages in this cluster
25    pub examples: Vec<String>,
26}
27
28/// A failure pattern that can be targeted for fixing
29#[derive(Debug, Clone)]
30pub struct FailurePattern {
31    /// Unique pattern identifier
32    pub id: String,
33    /// Error code this pattern addresses
34    pub error_code: String,
35    /// Description of the pattern
36    pub description: String,
37    /// Category (e.g., "type_inference", "external_deps", "borrowing")
38    pub category: PatternCategory,
39    /// How many files exhibit this pattern
40    pub affected_count: u32,
41    /// Estimated complexity to fix (1-10)
42    pub fix_complexity: u8,
43    /// Example Python code that triggers this
44    pub trigger_example: String,
45}
46
47/// Categories of failure patterns
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum PatternCategory {
50    /// Type inference failures (15% of total)
51    TypeInference,
52    /// External dependency issues (68% of total)
53    ExternalDeps,
54    /// Borrowing and lifetime issues (10% of total)
55    Borrowing,
56    /// Control flow (try/except, match) issues (5% of total)
57    ControlFlow,
58    /// Other/miscellaneous (2% of total)
59    Miscellaneous,
60}
61
62impl std::fmt::Display for PatternCategory {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            PatternCategory::TypeInference => write!(f, "Type Inference"),
66            PatternCategory::ExternalDeps => write!(f, "External Dependencies"),
67            PatternCategory::Borrowing => write!(f, "Borrowing"),
68            PatternCategory::ControlFlow => write!(f, "Control Flow"),
69            PatternCategory::Miscellaneous => write!(f, "Miscellaneous"),
70        }
71    }
72}
73
74/// Prioritized pattern for the work queue
75#[derive(Debug, Clone)]
76pub struct PrioritizedPattern {
77    pub pattern: FailurePattern,
78    /// Priority score: frequency × severity / complexity
79    pub priority: f64,
80}
81
82impl PartialEq for PrioritizedPattern {
83    fn eq(&self, other: &Self) -> bool {
84        self.priority == other.priority
85    }
86}
87
88impl Eq for PrioritizedPattern {}
89
90impl PartialOrd for PrioritizedPattern {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for PrioritizedPattern {
97    fn cmp(&self, other: &Self) -> Ordering {
98        // Higher priority first (max heap)
99        self.priority
100            .partial_cmp(&other.priority)
101            .unwrap_or(Ordering::Equal)
102    }
103}
104
105/// Hunt Planner: Classifies and prioritizes compilation failures
106///
107/// Implements Heijunka by processing highest-impact patterns first.
108#[derive(Debug)]
109pub struct HuntPlanner {
110    /// Clustered errors from analysis
111    error_clusters: Vec<ErrorCluster>,
112    /// Priority queue of patterns to fix
113    priority_queue: BinaryHeap<PrioritizedPattern>,
114    /// Patterns already processed
115    processed: HashMap<String, bool>,
116}
117
118impl HuntPlanner {
119    /// Create a new hunt planner
120    pub fn new() -> Self {
121        Self {
122            error_clusters: Vec::new(),
123            priority_queue: BinaryHeap::new(),
124            processed: HashMap::new(),
125        }
126    }
127
128    /// Add error clusters from corpus analysis
129    pub fn add_clusters(&mut self, clusters: Vec<ErrorCluster>) {
130        self.error_clusters.extend(clusters);
131    }
132
133    /// Analyze clusters and build priority queue
134    ///
135    /// Heijunka: Sort by frequency × severity / complexity for maximum impact
136    pub fn build_priority_queue(&mut self) {
137        for cluster in &self.error_clusters {
138            let pattern = self.cluster_to_pattern(cluster);
139            let priority = self.calculate_priority(&pattern);
140
141            self.priority_queue
142                .push(PrioritizedPattern { pattern, priority });
143        }
144    }
145
146    /// Select the next highest-priority failure pattern
147    ///
148    /// Heijunka: Process highest-impact patterns first.
149    /// Pareto principle: 20% of patterns cause 80% of failures.
150    pub fn select_next_target(&mut self) -> Option<FailurePattern> {
151        while let Some(prioritized) = self.priority_queue.pop() {
152            let pattern_id = &prioritized.pattern.id;
153
154            // Skip already processed patterns
155            if self.processed.get(pattern_id).copied().unwrap_or(false) {
156                continue;
157            }
158
159            self.processed.insert(pattern_id.clone(), true);
160            return Some(prioritized.pattern);
161        }
162        None
163    }
164
165    /// Calculate priority score for a pattern
166    ///
167    /// Formula: (frequency × severity) / complexity
168    /// Higher score = higher priority
169    fn calculate_priority(&self, pattern: &FailurePattern) -> f64 {
170        let frequency = pattern.affected_count as f64;
171        let complexity = pattern.fix_complexity as f64;
172
173        // Avoid division by zero
174        let complexity = complexity.max(1.0);
175
176        (frequency * 10.0) / complexity
177    }
178
179    /// Convert error cluster to failure pattern
180    fn cluster_to_pattern(&self, cluster: &ErrorCluster) -> FailurePattern {
181        let category = self.categorize_error(&cluster.error_code);
182
183        FailurePattern {
184            id: format!("pattern_{}", cluster.id),
185            error_code: cluster.error_code.clone(),
186            description: cluster.description.clone(),
187            category,
188            affected_count: cluster.frequency,
189            fix_complexity: self.estimate_complexity(&cluster.error_code),
190            trigger_example: cluster.examples.first().cloned().unwrap_or_default(),
191        }
192    }
193
194    /// Categorize error code into pattern category
195    fn categorize_error(&self, error_code: &str) -> PatternCategory {
196        match error_code {
197            // Type mismatch errors
198            "E0308" | "E0277" | "E0282" => PatternCategory::TypeInference,
199            // Unresolved import errors
200            "E0432" | "E0433" => PatternCategory::ExternalDeps,
201            // Borrowing errors
202            "E0502" | "E0503" | "E0505" | "E0506" | "E0507" => PatternCategory::Borrowing,
203            // Move errors (related to borrowing)
204            "E0382" | "E0383" => PatternCategory::Borrowing,
205            // Lifetime errors
206            "E0106" | "E0621" | "E0623" => PatternCategory::Borrowing,
207            // Control flow (not exhaustive match, etc.)
208            "E0004" | "E0005" => PatternCategory::ControlFlow,
209            // Default to miscellaneous
210            _ => PatternCategory::Miscellaneous,
211        }
212    }
213
214    /// Estimate fix complexity based on error code
215    fn estimate_complexity(&self, error_code: &str) -> u8 {
216        match error_code {
217            // Easy: just add imports
218            "E0432" | "E0433" => 2,
219            // Medium: type coercion
220            "E0308" => 4,
221            // Medium-hard: trait bounds
222            "E0277" => 5,
223            // Hard: borrowing issues
224            "E0502" | "E0503" | "E0505" | "E0506" | "E0507" => 7,
225            // Very hard: lifetime issues
226            "E0106" | "E0621" | "E0623" => 8,
227            // Default medium complexity
228            _ => 5,
229        }
230    }
231
232    /// Get remaining patterns count
233    pub fn remaining_count(&self) -> usize {
234        self.priority_queue.len()
235    }
236
237    /// Get all clusters
238    pub fn clusters(&self) -> &[ErrorCluster] {
239        &self.error_clusters
240    }
241}
242
243impl Default for HuntPlanner {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    fn create_test_cluster(id: &str, code: &str, freq: u32) -> ErrorCluster {
254        ErrorCluster {
255            id: id.to_string(),
256            error_code: code.to_string(),
257            description: format!("Test error {}", code),
258            frequency: freq,
259            severity: 5,
260            examples: vec!["example".to_string()],
261        }
262    }
263
264    #[test]
265    fn test_planner_new() {
266        let planner = HuntPlanner::new();
267        assert_eq!(planner.remaining_count(), 0);
268        assert!(planner.clusters().is_empty());
269    }
270
271    #[test]
272    fn test_add_clusters() {
273        let mut planner = HuntPlanner::new();
274        planner.add_clusters(vec![
275            create_test_cluster("1", "E0308", 10),
276            create_test_cluster("2", "E0432", 20),
277        ]);
278        assert_eq!(planner.clusters().len(), 2);
279    }
280
281    #[test]
282    fn test_build_priority_queue() {
283        let mut planner = HuntPlanner::new();
284        planner.add_clusters(vec![
285            create_test_cluster("1", "E0308", 10),
286            create_test_cluster("2", "E0432", 20),
287        ]);
288        planner.build_priority_queue();
289        assert_eq!(planner.remaining_count(), 2);
290    }
291
292    #[test]
293    fn test_select_next_target_priority_order() {
294        let mut planner = HuntPlanner::new();
295        planner.add_clusters(vec![
296            create_test_cluster("low", "E0308", 5), // Lower freq, higher complexity
297            create_test_cluster("high", "E0432", 50), // Higher freq, lower complexity
298        ]);
299        planner.build_priority_queue();
300
301        // Should select high-frequency, low-complexity first
302        let first = planner.select_next_target().unwrap();
303        assert_eq!(first.error_code, "E0432"); // External deps, easier to fix
304    }
305
306    #[test]
307    fn test_select_next_target_no_duplicates() {
308        let mut planner = HuntPlanner::new();
309        planner.add_clusters(vec![create_test_cluster("1", "E0308", 10)]);
310        planner.build_priority_queue();
311
312        assert!(planner.select_next_target().is_some());
313        assert!(planner.select_next_target().is_none()); // Already processed
314    }
315
316    #[test]
317    fn test_categorize_error() {
318        let planner = HuntPlanner::new();
319
320        assert_eq!(
321            planner.categorize_error("E0308"),
322            PatternCategory::TypeInference
323        );
324        assert_eq!(
325            planner.categorize_error("E0432"),
326            PatternCategory::ExternalDeps
327        );
328        assert_eq!(
329            planner.categorize_error("E0502"),
330            PatternCategory::Borrowing
331        );
332        assert_eq!(
333            planner.categorize_error("E0004"),
334            PatternCategory::ControlFlow
335        );
336        assert_eq!(
337            planner.categorize_error("E9999"),
338            PatternCategory::Miscellaneous
339        );
340    }
341
342    #[test]
343    fn test_estimate_complexity() {
344        let planner = HuntPlanner::new();
345
346        // Import errors should be easy
347        assert!(planner.estimate_complexity("E0432") < 5);
348        // Lifetime errors should be hard
349        assert!(planner.estimate_complexity("E0106") > 5);
350    }
351
352    #[test]
353    fn test_pattern_category_display() {
354        assert_eq!(
355            format!("{}", PatternCategory::TypeInference),
356            "Type Inference"
357        );
358        assert_eq!(
359            format!("{}", PatternCategory::ExternalDeps),
360            "External Dependencies"
361        );
362    }
363
364    // DEPYLER-COVERAGE-95: Additional tests for untested components
365
366    #[test]
367    fn test_error_cluster_debug() {
368        let cluster = create_test_cluster("1", "E0308", 10);
369        let debug_str = format!("{:?}", cluster);
370        assert!(debug_str.contains("ErrorCluster"));
371        assert!(debug_str.contains("E0308"));
372    }
373
374    #[test]
375    fn test_error_cluster_clone() {
376        let cluster = create_test_cluster("orig", "E0432", 20);
377        let cloned = cluster.clone();
378        assert_eq!(cloned.id, "orig");
379        assert_eq!(cloned.error_code, "E0432");
380        assert_eq!(cloned.frequency, 20);
381    }
382
383    #[test]
384    fn test_failure_pattern_debug() {
385        let planner = HuntPlanner::new();
386        let cluster = create_test_cluster("1", "E0308", 10);
387        let pattern = planner.cluster_to_pattern(&cluster);
388
389        let debug_str = format!("{:?}", pattern);
390        assert!(debug_str.contains("FailurePattern"));
391        assert!(debug_str.contains("E0308"));
392    }
393
394    #[test]
395    fn test_failure_pattern_clone() {
396        let planner = HuntPlanner::new();
397        let cluster = create_test_cluster("1", "E0277", 15);
398        let pattern = planner.cluster_to_pattern(&cluster);
399        let cloned = pattern.clone();
400
401        assert_eq!(cloned.error_code, "E0277");
402        assert_eq!(cloned.affected_count, 15);
403    }
404
405    #[test]
406    fn test_pattern_category_debug() {
407        let cat = PatternCategory::TypeInference;
408        let debug_str = format!("{:?}", cat);
409        assert!(debug_str.contains("TypeInference"));
410    }
411
412    #[test]
413    fn test_pattern_category_copy() {
414        let cat = PatternCategory::Borrowing;
415        let copied = cat;
416        assert_eq!(copied, PatternCategory::Borrowing);
417    }
418
419    #[test]
420    fn test_pattern_category_eq() {
421        assert_eq!(PatternCategory::ControlFlow, PatternCategory::ControlFlow);
422        assert_ne!(PatternCategory::ControlFlow, PatternCategory::Borrowing);
423    }
424
425    #[test]
426    fn test_pattern_category_hash() {
427        use std::collections::HashSet;
428        let mut set = HashSet::new();
429        set.insert(PatternCategory::TypeInference);
430        set.insert(PatternCategory::TypeInference);
431        set.insert(PatternCategory::ExternalDeps);
432        assert_eq!(set.len(), 2);
433    }
434
435    #[test]
436    fn test_pattern_category_display_all() {
437        assert_eq!(format!("{}", PatternCategory::Borrowing), "Borrowing");
438        assert_eq!(format!("{}", PatternCategory::ControlFlow), "Control Flow");
439        assert_eq!(
440            format!("{}", PatternCategory::Miscellaneous),
441            "Miscellaneous"
442        );
443    }
444
445    #[test]
446    fn test_prioritized_pattern_debug() {
447        let planner = HuntPlanner::new();
448        let cluster = create_test_cluster("1", "E0308", 10);
449        let pattern = planner.cluster_to_pattern(&cluster);
450        let prioritized = PrioritizedPattern {
451            pattern,
452            priority: 5.0,
453        };
454
455        let debug_str = format!("{:?}", prioritized);
456        assert!(debug_str.contains("PrioritizedPattern"));
457        assert!(debug_str.contains("priority"));
458    }
459
460    #[test]
461    fn test_prioritized_pattern_clone() {
462        let planner = HuntPlanner::new();
463        let cluster = create_test_cluster("1", "E0432", 25);
464        let pattern = planner.cluster_to_pattern(&cluster);
465        let prioritized = PrioritizedPattern {
466            pattern,
467            priority: 12.5,
468        };
469        let cloned = prioritized.clone();
470
471        assert_eq!(cloned.priority, 12.5);
472        assert_eq!(cloned.pattern.error_code, "E0432");
473    }
474
475    #[test]
476    fn test_prioritized_pattern_eq() {
477        let planner = HuntPlanner::new();
478        let cluster1 = create_test_cluster("1", "E0308", 10);
479        let cluster2 = create_test_cluster("2", "E0432", 20);
480        let pattern1 = planner.cluster_to_pattern(&cluster1);
481        let pattern2 = planner.cluster_to_pattern(&cluster2);
482
483        let p1 = PrioritizedPattern {
484            pattern: pattern1,
485            priority: 5.0,
486        };
487        let p2 = PrioritizedPattern {
488            pattern: pattern2,
489            priority: 5.0,
490        };
491
492        assert_eq!(p1, p2); // Same priority = equal
493    }
494
495    #[test]
496    fn test_prioritized_pattern_ord() {
497        let planner = HuntPlanner::new();
498        let cluster = create_test_cluster("1", "E0308", 10);
499        let pattern = planner.cluster_to_pattern(&cluster);
500
501        let low = PrioritizedPattern {
502            pattern: pattern.clone(),
503            priority: 1.0,
504        };
505        let high = PrioritizedPattern {
506            pattern,
507            priority: 10.0,
508        };
509
510        assert!(high > low);
511        assert!(low < high);
512    }
513
514    #[test]
515    fn test_hunt_planner_default() {
516        let planner: HuntPlanner = Default::default();
517        assert!(planner.clusters().is_empty());
518        assert_eq!(planner.remaining_count(), 0);
519    }
520
521    #[test]
522    fn test_hunt_planner_debug() {
523        let planner = HuntPlanner::new();
524        let debug_str = format!("{:?}", planner);
525        assert!(debug_str.contains("HuntPlanner"));
526    }
527
528    #[test]
529    fn test_remaining_count_after_selection() {
530        let mut planner = HuntPlanner::new();
531        planner.add_clusters(vec![
532            create_test_cluster("1", "E0308", 10),
533            create_test_cluster("2", "E0432", 20),
534        ]);
535        planner.build_priority_queue();
536
537        assert_eq!(planner.remaining_count(), 2);
538        planner.select_next_target();
539        assert_eq!(planner.remaining_count(), 1);
540        planner.select_next_target();
541        assert_eq!(planner.remaining_count(), 0);
542    }
543
544    #[test]
545    fn test_calculate_priority() {
546        let planner = HuntPlanner::new();
547        let mut cluster = create_test_cluster("1", "E0308", 100);
548        cluster.frequency = 100;
549        let pattern = planner.cluster_to_pattern(&cluster);
550
551        // Priority = (frequency * 10) / complexity
552        // E0308 has complexity 4, so priority = (100 * 10) / 4 = 250
553        let priority = planner.calculate_priority(&pattern);
554        assert!(priority > 200.0);
555    }
556
557    #[test]
558    fn test_calculate_priority_zero_complexity() {
559        let planner = HuntPlanner::new();
560        let pattern = FailurePattern {
561            id: "test".to_string(),
562            error_code: "E0000".to_string(),
563            description: "test".to_string(),
564            category: PatternCategory::Miscellaneous,
565            affected_count: 10,
566            fix_complexity: 0, // Zero complexity (edge case)
567            trigger_example: String::new(),
568        };
569
570        // Should not panic, complexity clamped to 1.0
571        let priority = planner.calculate_priority(&pattern);
572        assert!(priority > 0.0);
573    }
574
575    #[test]
576    fn test_categorize_all_error_codes() {
577        let planner = HuntPlanner::new();
578
579        // Type inference
580        assert_eq!(
581            planner.categorize_error("E0282"),
582            PatternCategory::TypeInference
583        );
584
585        // External deps
586        assert_eq!(
587            planner.categorize_error("E0433"),
588            PatternCategory::ExternalDeps
589        );
590
591        // Borrowing - various codes
592        assert_eq!(
593            planner.categorize_error("E0503"),
594            PatternCategory::Borrowing
595        );
596        assert_eq!(
597            planner.categorize_error("E0505"),
598            PatternCategory::Borrowing
599        );
600        assert_eq!(
601            planner.categorize_error("E0506"),
602            PatternCategory::Borrowing
603        );
604        assert_eq!(
605            planner.categorize_error("E0507"),
606            PatternCategory::Borrowing
607        );
608        assert_eq!(
609            planner.categorize_error("E0382"),
610            PatternCategory::Borrowing
611        );
612        assert_eq!(
613            planner.categorize_error("E0383"),
614            PatternCategory::Borrowing
615        );
616
617        // Lifetime errors
618        assert_eq!(
619            planner.categorize_error("E0106"),
620            PatternCategory::Borrowing
621        );
622        assert_eq!(
623            planner.categorize_error("E0621"),
624            PatternCategory::Borrowing
625        );
626        assert_eq!(
627            planner.categorize_error("E0623"),
628            PatternCategory::Borrowing
629        );
630
631        // Control flow
632        assert_eq!(
633            planner.categorize_error("E0005"),
634            PatternCategory::ControlFlow
635        );
636    }
637
638    #[test]
639    fn test_estimate_complexity_all_codes() {
640        let planner = HuntPlanner::new();
641
642        // Easy imports
643        assert!(planner.estimate_complexity("E0433") <= 3);
644
645        // Medium type mismatch
646        let e0308_complexity = planner.estimate_complexity("E0308");
647        assert!((3..=5).contains(&e0308_complexity));
648
649        // Hard borrowing
650        assert!(planner.estimate_complexity("E0503") >= 6);
651        assert!(planner.estimate_complexity("E0505") >= 6);
652
653        // Very hard lifetime
654        assert!(planner.estimate_complexity("E0621") >= 7);
655        assert!(planner.estimate_complexity("E0623") >= 7);
656    }
657
658    #[test]
659    fn test_cluster_to_pattern_with_examples() {
660        let planner = HuntPlanner::new();
661        let mut cluster = create_test_cluster("1", "E0308", 10);
662        cluster.examples = vec!["first example".to_string(), "second".to_string()];
663
664        let pattern = planner.cluster_to_pattern(&cluster);
665        assert_eq!(pattern.trigger_example, "first example");
666    }
667
668    #[test]
669    fn test_cluster_to_pattern_no_examples() {
670        let planner = HuntPlanner::new();
671        let mut cluster = create_test_cluster("1", "E0308", 10);
672        cluster.examples.clear();
673
674        let pattern = planner.cluster_to_pattern(&cluster);
675        assert!(pattern.trigger_example.is_empty());
676    }
677
678    #[test]
679    fn test_select_empty_queue() {
680        let mut planner = HuntPlanner::new();
681        assert!(planner.select_next_target().is_none());
682    }
683}