Skip to main content

depyler_tooling/hunt_mode/
mod.rs

1//! Hunt Mode: Automated Calibration Subsystem for Depyler
2//!
3//! Implements Toyota Production System principles for continuous compiler improvement:
4//! - Kaizen (改善): Continuous incremental improvement tracking
5//! - Jidoka (自働化): Automation with quality gates
6//! - Andon (行灯): Visual status and stop-the-line signaling
7//! - Hansei (反省): Reflection and lessons learned
8//! - Genchi Genbutsu (現地現物): Direct observation of failures
9//!
10//! # Architecture
11//!
12//! Hunt Mode operates as a PDCA (Plan-Do-Check-Act) cycle:
13//! 1. PLAN (Hunt): Classify and prioritize compilation failures
14//! 2. DO (Isolate): Synthesize minimal reproduction cases
15//! 3. CHECK (Repair): Apply heuristic fixes with quality thresholds
16//! 4. ACT (Verify): Validate and commit successful fixes
17//!
18//! # References
19//!
20//! - Liker, J.K. (2004). The Toyota Way
21//! - Ohno, T. (1988). Toyota Production System
22//! - Beck, K. (2002). Test Driven Development
23
24pub mod five_whys;
25pub mod hansei;
26pub mod hunt_shim;
27pub mod isolator;
28pub mod kaizen;
29pub mod planner;
30pub mod repair;
31pub mod verifier;
32
33// Re-exports for convenience
34pub use five_whys::{FiveWhysAnalyzer, RootCauseChain, WhyStep};
35pub use hansei::{CycleOutcome, HanseiReflector, Lesson};
36pub use isolator::{MinimalReproducer, ReproCase};
37pub use kaizen::KaizenMetrics;
38pub use planner::{ErrorCluster, FailurePattern, HuntPlanner};
39pub use repair::{JidokaRepairEngine, Mutator, RepairResult};
40pub use verifier::{AndonStatus, AndonVerifier, VerifyResult};
41
42use std::path::PathBuf;
43
44/// Configuration for Hunt Mode operation
45#[derive(Debug, Clone)]
46pub struct HuntConfig {
47    /// Maximum number of cycles to run
48    pub max_cycles: u32,
49    /// Minimum confidence threshold for auto-applying fixes (Jidoka)
50    pub quality_threshold: f64,
51    /// Target compilation rate (Kaizen goal)
52    pub target_rate: f64,
53    /// Stop if no improvement after this many cycles
54    pub plateau_threshold: u32,
55    /// Enable Five Whys analysis
56    pub enable_five_whys: bool,
57    /// Path to lessons database
58    pub lessons_database: PathBuf,
59}
60
61impl Default for HuntConfig {
62    fn default() -> Self {
63        Self {
64            max_cycles: 100,
65            quality_threshold: 0.85,
66            target_rate: 0.80,
67            plateau_threshold: 5,
68            enable_five_whys: true,
69            lessons_database: PathBuf::from(".depyler/lessons.db"),
70        }
71    }
72}
73
74/// Main Hunt Mode engine that orchestrates the PDCA cycle
75pub struct HuntEngine {
76    config: HuntConfig,
77    metrics: KaizenMetrics,
78    planner: HuntPlanner,
79    reproducer: MinimalReproducer,
80    repair_engine: JidokaRepairEngine,
81    verifier: AndonVerifier,
82    reflector: HanseiReflector,
83}
84
85impl HuntEngine {
86    /// Create a new Hunt Mode engine with the given configuration
87    pub fn new(config: HuntConfig) -> Self {
88        Self {
89            metrics: KaizenMetrics::new(),
90            planner: HuntPlanner::new(),
91            reproducer: MinimalReproducer::new(),
92            repair_engine: JidokaRepairEngine::new(config.quality_threshold),
93            verifier: AndonVerifier::new(),
94            reflector: HanseiReflector::new(),
95            config,
96        }
97    }
98
99    /// Run a single PDCA cycle
100    ///
101    /// Returns the outcome of the cycle for Hansei reflection
102    pub fn run_cycle(&mut self) -> anyhow::Result<CycleOutcome> {
103        // PLAN: Select highest-priority failure pattern
104        let pattern = self
105            .planner
106            .select_next_target()
107            .ok_or_else(|| anyhow::anyhow!("No failure patterns to process"))?;
108
109        // DO: Synthesize minimal reproduction case
110        let repro = self.reproducer.synthesize_repro(&pattern)?;
111
112        // CHECK: Attempt repair with Jidoka quality gates
113        let repair_result = self.repair_engine.attempt_repair(&repro)?;
114
115        // ACT: Verify and commit if successful
116        let verify_result = match repair_result {
117            RepairResult::Success(fix) => self.verifier.verify_and_commit(&fix, &repro)?,
118            RepairResult::NeedsHumanReview {
119                fix,
120                confidence,
121                reason,
122            } => VerifyResult::NeedsReview {
123                fix,
124                confidence,
125                reason,
126            },
127            RepairResult::NoFixFound => VerifyResult::NoFixAvailable,
128        };
129
130        // Create outcome for Hansei reflection
131        let outcome = CycleOutcome {
132            pattern,
133            repro,
134            verify_result,
135            metrics_snapshot: self.metrics.clone(),
136        };
137
138        // Hansei: Reflect and learn
139        let _lessons = self.reflector.reflect_on_cycle(&outcome);
140
141        // Update Kaizen metrics
142        self.metrics.record_cycle(&outcome);
143
144        Ok(outcome)
145    }
146
147    /// Run Hunt Mode until target rate achieved or plateau detected
148    pub fn run_until_complete(&mut self) -> anyhow::Result<Vec<CycleOutcome>> {
149        let mut outcomes = Vec::new();
150
151        for cycle in 0..self.config.max_cycles {
152            // Check if target achieved (Kaizen goal)
153            if self.metrics.compilation_rate >= self.config.target_rate {
154                tracing::info!(
155                    "Target rate {:.1}% achieved after {} cycles",
156                    self.config.target_rate * 100.0,
157                    cycle
158                );
159                break;
160            }
161
162            // Check for plateau (Andon: stop if no progress)
163            if self.metrics.cycles_since_improvement >= self.config.plateau_threshold {
164                tracing::warn!(
165                    "Plateau detected after {} cycles without improvement",
166                    self.metrics.cycles_since_improvement
167                );
168                break;
169            }
170
171            match self.run_cycle() {
172                Ok(outcome) => outcomes.push(outcome),
173                Err(e) => {
174                    tracing::error!("Cycle {} failed: {}", cycle, e);
175                    // Andon: Don't stop completely, log and continue
176                }
177            }
178        }
179
180        Ok(outcomes)
181    }
182
183    /// Get current Andon status
184    pub fn andon_status(&self) -> &AndonStatus {
185        self.verifier.status()
186    }
187
188    /// Get current Kaizen metrics
189    pub fn metrics(&self) -> &KaizenMetrics {
190        &self.metrics
191    }
192
193    /// Export Hansei lessons learned
194    pub fn export_lessons(&self) -> Vec<Lesson> {
195        self.reflector.lessons().to_vec()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_hunt_config_default() {
205        let config = HuntConfig::default();
206        assert_eq!(config.max_cycles, 100);
207        assert!((config.quality_threshold - 0.85).abs() < f64::EPSILON);
208        assert!((config.target_rate - 0.80).abs() < f64::EPSILON);
209        assert_eq!(config.plateau_threshold, 5);
210        assert!(config.enable_five_whys);
211    }
212
213    #[test]
214    fn test_hunt_config_custom() {
215        let config = HuntConfig {
216            max_cycles: 50,
217            quality_threshold: 0.90,
218            target_rate: 0.95,
219            plateau_threshold: 10,
220            enable_five_whys: false,
221            lessons_database: PathBuf::from("/custom/path"),
222        };
223        assert_eq!(config.max_cycles, 50);
224        assert!((config.quality_threshold - 0.90).abs() < f64::EPSILON);
225        assert!((config.target_rate - 0.95).abs() < f64::EPSILON);
226        assert_eq!(config.plateau_threshold, 10);
227        assert!(!config.enable_five_whys);
228        assert_eq!(config.lessons_database.to_str(), Some("/custom/path"));
229    }
230
231    #[test]
232    fn test_hunt_config_clone() {
233        let config = HuntConfig::default();
234        let cloned = config.clone();
235        assert_eq!(cloned.max_cycles, config.max_cycles);
236        assert!((cloned.quality_threshold - config.quality_threshold).abs() < f64::EPSILON);
237    }
238
239    #[test]
240    fn test_hunt_config_debug() {
241        let config = HuntConfig::default();
242        let debug_str = format!("{:?}", config);
243        assert!(debug_str.contains("HuntConfig"));
244        assert!(debug_str.contains("max_cycles"));
245        assert!(debug_str.contains("quality_threshold"));
246    }
247
248    #[test]
249    fn test_hunt_engine_creation() {
250        let config = HuntConfig::default();
251        let engine = HuntEngine::new(config);
252        assert_eq!(engine.metrics().compilation_rate, 0.0);
253        assert_eq!(engine.metrics().cumulative_fixes, 0);
254    }
255
256    #[test]
257    fn test_hunt_engine_andon_status() {
258        let config = HuntConfig::default();
259        let engine = HuntEngine::new(config);
260        let status = engine.andon_status();
261        // Just verify we can call the method and get a status
262        // (the actual status value depends on initial state)
263        let _ = format!("{:?}", status); // Ensure Debug works
264    }
265
266    #[test]
267    fn test_hunt_engine_export_lessons_empty() {
268        let config = HuntConfig::default();
269        let engine = HuntEngine::new(config);
270        let lessons = engine.export_lessons();
271        assert!(lessons.is_empty());
272    }
273
274    #[test]
275    fn test_hunt_engine_with_custom_config() {
276        let config = HuntConfig {
277            max_cycles: 10,
278            quality_threshold: 0.99,
279            target_rate: 0.5,
280            plateau_threshold: 2,
281            enable_five_whys: false,
282            lessons_database: PathBuf::from("test.db"),
283        };
284        let engine = HuntEngine::new(config);
285        assert_eq!(engine.metrics().compilation_rate, 0.0);
286    }
287
288    // DEPYLER-COVERAGE-95: Additional tests for untested components
289
290    #[test]
291    fn test_hunt_config_all_fields() {
292        let config = HuntConfig::default();
293        // Verify all fields are accessible
294        assert_eq!(config.max_cycles, 100);
295        assert!((config.quality_threshold - 0.85).abs() < f64::EPSILON);
296        assert!((config.target_rate - 0.80).abs() < f64::EPSILON);
297        assert_eq!(config.plateau_threshold, 5);
298        assert!(config.enable_five_whys);
299        assert_eq!(
300            config.lessons_database,
301            PathBuf::from(".depyler/lessons.db")
302        );
303    }
304
305    #[test]
306    fn test_hunt_config_lessons_database_custom() {
307        let config = HuntConfig {
308            lessons_database: PathBuf::from("/custom/db/lessons.sqlite"),
309            ..Default::default()
310        };
311        assert_eq!(
312            config.lessons_database,
313            PathBuf::from("/custom/db/lessons.sqlite")
314        );
315    }
316
317    #[test]
318    fn test_hunt_engine_metrics_initial_state() {
319        let config = HuntConfig::default();
320        let engine = HuntEngine::new(config);
321        let metrics = engine.metrics();
322
323        assert_eq!(metrics.compilation_rate, 0.0);
324        assert_eq!(metrics.cumulative_fixes, 0);
325        assert_eq!(metrics.cycles_since_improvement, 0);
326    }
327
328    #[test]
329    fn test_hunt_engine_export_lessons_type() {
330        let config = HuntConfig::default();
331        let engine = HuntEngine::new(config);
332        let lessons: Vec<Lesson> = engine.export_lessons();
333
334        // Initially empty
335        assert!(lessons.is_empty());
336        // Verify it's a Vec<Lesson>
337        let _: &[Lesson] = &lessons;
338    }
339
340    #[test]
341    fn test_hunt_engine_andon_status_type() {
342        let config = HuntConfig::default();
343        let engine = HuntEngine::new(config);
344        let status: &AndonStatus = engine.andon_status();
345
346        // Verify it's the right type and can be formatted
347        let _debug = format!("{:?}", status);
348    }
349
350    #[test]
351    fn test_hunt_config_debug_format() {
352        let config = HuntConfig {
353            max_cycles: 50,
354            quality_threshold: 0.95,
355            target_rate: 0.90,
356            plateau_threshold: 3,
357            enable_five_whys: false,
358            lessons_database: PathBuf::from("test.db"),
359        };
360
361        let debug = format!("{:?}", config);
362        assert!(debug.contains("max_cycles"));
363        assert!(debug.contains("50"));
364        assert!(debug.contains("quality_threshold"));
365        assert!(debug.contains("0.95"));
366        assert!(debug.contains("target_rate"));
367        assert!(debug.contains("0.9"));
368        assert!(debug.contains("plateau_threshold"));
369        assert!(debug.contains("3"));
370        assert!(debug.contains("enable_five_whys"));
371        assert!(debug.contains("false"));
372        assert!(debug.contains("lessons_database"));
373        assert!(debug.contains("test.db"));
374    }
375
376    #[test]
377    fn test_hunt_config_clone_independence() {
378        let original = HuntConfig {
379            max_cycles: 25,
380            quality_threshold: 0.75,
381            target_rate: 0.60,
382            plateau_threshold: 8,
383            enable_five_whys: true,
384            lessons_database: PathBuf::from("original.db"),
385        };
386
387        let mut cloned = original.clone();
388        cloned.max_cycles = 50;
389        cloned.quality_threshold = 0.99;
390
391        // Original unchanged
392        assert_eq!(original.max_cycles, 25);
393        assert!((original.quality_threshold - 0.75).abs() < f64::EPSILON);
394
395        // Cloned has new values
396        assert_eq!(cloned.max_cycles, 50);
397        assert!((cloned.quality_threshold - 0.99).abs() < f64::EPSILON);
398    }
399
400    #[test]
401    fn test_hunt_config_edge_values() {
402        // Test with edge/boundary values
403        let config = HuntConfig {
404            max_cycles: 0,
405            quality_threshold: 0.0,
406            target_rate: 1.0,
407            plateau_threshold: u32::MAX,
408            enable_five_whys: false,
409            lessons_database: PathBuf::new(),
410        };
411
412        assert_eq!(config.max_cycles, 0);
413        assert_eq!(config.quality_threshold, 0.0);
414        assert_eq!(config.target_rate, 1.0);
415        assert_eq!(config.plateau_threshold, u32::MAX);
416        assert!(!config.enable_five_whys);
417        assert_eq!(config.lessons_database, PathBuf::new());
418    }
419
420    #[test]
421    fn test_hunt_engine_config_preserved() {
422        let config = HuntConfig {
423            max_cycles: 42,
424            quality_threshold: 0.77,
425            target_rate: 0.88,
426            plateau_threshold: 7,
427            enable_five_whys: true,
428            lessons_database: PathBuf::from("preserved.db"),
429        };
430
431        let engine = HuntEngine::new(config);
432
433        // Engine should be functional
434        assert_eq!(engine.metrics().compilation_rate, 0.0);
435        assert!(engine.export_lessons().is_empty());
436    }
437
438    #[test]
439    fn test_hunt_engine_multiple_instances() {
440        let config1 = HuntConfig::default();
441        let config2 = HuntConfig {
442            max_cycles: 10,
443            ..Default::default()
444        };
445
446        let engine1 = HuntEngine::new(config1);
447        let engine2 = HuntEngine::new(config2);
448
449        // Both engines independent
450        assert_eq!(engine1.metrics().compilation_rate, 0.0);
451        assert_eq!(engine2.metrics().compilation_rate, 0.0);
452    }
453
454    #[test]
455    fn test_re_exports_available() {
456        // Verify re-exports work
457        let _: KaizenMetrics = KaizenMetrics::new();
458        let _: HuntPlanner = HuntPlanner::new();
459        let _: MinimalReproducer = MinimalReproducer::new();
460        let _: JidokaRepairEngine = JidokaRepairEngine::new(0.85);
461        let _: AndonVerifier = AndonVerifier::new();
462        let _: HanseiReflector = HanseiReflector::new();
463        let _: FiveWhysAnalyzer = FiveWhysAnalyzer::new();
464    }
465}