Skip to main content

oris_evokernel/
signal_extractor.rs

1//! Runtime Signal Extraction Module
2//!
3//! This module provides runtime signal extraction capabilities for the evolution loop:
4//! - Compiler diagnostics parsing (rustc, clang, etc.)
5//! - Stack trace parsing
6//! - Execution log analysis
7//! - Failure pattern detection
8//!
9//! These signals are used to drive the Detect phase of the evolution loop.
10
11use regex_lite::Regex;
12use serde::{Deserialize, Serialize};
13use std::collections::HashSet;
14
15/// A signal extracted from runtime execution
16#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
17pub struct RuntimeSignal {
18    /// Unique signal ID
19    pub signal_id: String,
20    /// Signal type
21    pub signal_type: RuntimeSignalType,
22    /// Signal content/description
23    pub content: String,
24    /// Confidence score (0.0 - 1.0)
25    pub confidence: f32,
26    /// Source location (file:line)
27    pub location: Option<String>,
28    /// Additional metadata
29    pub metadata: serde_json::Value,
30}
31
32/// Types of runtime signals
33#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
34#[serde(rename_all = "snake_case")]
35pub enum RuntimeSignalType {
36    /// Compiler error or warning
37    CompilerDiagnostic,
38    /// Runtime panic or crash
39    RuntimePanic,
40    /// Timeout
41    Timeout,
42    /// Test failure
43    TestFailure,
44    /// Performance issue
45    PerformanceIssue,
46    /// Resource exhaustion (memory, disk, etc)
47    ResourceExhaustion,
48    /// Configuration error
49    ConfigError,
50    /// Security issue
51    SecurityIssue,
52    /// Generic error
53    GenericError,
54}
55
56impl RuntimeSignal {
57    /// Create a new runtime signal
58    pub fn new(signal_type: RuntimeSignalType, content: String, confidence: f32) -> Self {
59        Self {
60            signal_id: uuid::Uuid::new_v4().to_string(),
61            signal_type,
62            content,
63            confidence,
64            location: None,
65            metadata: serde_json::json!({}),
66        }
67    }
68
69    /// Create with location
70    pub fn with_location(mut self, location: String) -> Self {
71        self.location = Some(location);
72        self
73    }
74
75    /// Create with metadata
76    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
77        self.metadata = metadata;
78        self
79    }
80}
81
82/// Compiler diagnostics parser
83pub struct CompilerDiagnosticsParser {
84    /// Patterns for common compiler errors
85    rustc_error_patterns: Vec<(Regex, RuntimeSignalType)>,
86    rustc_warning_patterns: Vec<(Regex, RuntimeSignalType)>,
87}
88
89impl CompilerDiagnosticsParser {
90    /// Create a new parser with default patterns
91    pub fn new() -> Self {
92        let rustc_error_patterns = vec![
93            // Rust errors
94            (
95                Regex::new(r"error\[E\d+\]:\s*(.+)").unwrap(),
96                RuntimeSignalType::CompilerDiagnostic,
97            ),
98            (
99                Regex::new(r"^(?i)borrow checker error").unwrap(),
100                RuntimeSignalType::CompilerDiagnostic,
101            ),
102            (
103                Regex::new(r"^(?i)cannot find (function|struct|module|trait|macro)").unwrap(),
104                RuntimeSignalType::CompilerDiagnostic,
105            ),
106            (
107                Regex::new(r"^(?i)unresolved (import|item)").unwrap(),
108                RuntimeSignalType::CompilerDiagnostic,
109            ),
110            (
111                Regex::new(r"^(?i)type mismatch").unwrap(),
112                RuntimeSignalType::CompilerDiagnostic,
113            ),
114            (
115                Regex::new(r"^(?i)mismatched types").unwrap(),
116                RuntimeSignalType::CompilerDiagnostic,
117            ),
118            (
119                Regex::new(r"^(?i)expected (struct|enum|tuple|array)").unwrap(),
120                RuntimeSignalType::CompilerDiagnostic,
121            ),
122            (
123                Regex::new(r"^(?i)trait bound.*not satisfied").unwrap(),
124                RuntimeSignalType::CompilerDiagnostic,
125            ),
126            (
127                Regex::new(r"^(?i)the trait.*is not implemented").unwrap(),
128                RuntimeSignalType::CompilerDiagnostic,
129            ),
130            (
131                Regex::new(r"^(?i)method.*not found").unwrap(),
132                RuntimeSignalType::CompilerDiagnostic,
133            ),
134            (
135                Regex::new(r"^(?i)no method named.*found").unwrap(),
136                RuntimeSignalType::CompilerDiagnostic,
137            ),
138            (
139                Regex::new(r"^(?i)cannot borrow.*as.*mutable").unwrap(),
140                RuntimeSignalType::CompilerDiagnostic,
141            ),
142            (
143                Regex::new(r"^(?i)cannot borrow.*as.*immutable").unwrap(),
144                RuntimeSignalType::CompilerDiagnostic,
145            ),
146            (
147                Regex::new(r"^(?i)lifetime.*required").unwrap(),
148                RuntimeSignalType::CompilerDiagnostic,
149            ),
150        ];
151
152        let rustc_warning_patterns = vec![
153            (
154                Regex::new(r"warning\[W\d+\]:\s*(.+)").unwrap(),
155                RuntimeSignalType::CompilerDiagnostic,
156            ),
157            (
158                Regex::new(r"^(?i)unused (import|variable|function|struct)").unwrap(),
159                RuntimeSignalType::CompilerDiagnostic,
160            ),
161            (
162                Regex::new(r"^(?i)dead code").unwrap(),
163                RuntimeSignalType::CompilerDiagnostic,
164            ),
165            (
166                Regex::new(r"^(?i)field is never read").unwrap(),
167                RuntimeSignalType::CompilerDiagnostic,
168            ),
169            (
170                Regex::new(r"^(?i)deprecated").unwrap(),
171                RuntimeSignalType::CompilerDiagnostic,
172            ),
173        ];
174
175        Self {
176            rustc_error_patterns,
177            rustc_warning_patterns,
178        }
179    }
180
181    /// Parse compiler output and extract signals
182    pub fn parse(&self, output: &str) -> Vec<RuntimeSignal> {
183        let mut signals = Vec::new();
184
185        for line in output.lines() {
186            // Check error patterns
187            for (pattern, signal_type) in &self.rustc_error_patterns {
188                if let Some(caps) = pattern.captures(line) {
189                    let content = caps.get(1).map(|m| m.as_str()).unwrap_or(line);
190                    signals.push(RuntimeSignal::new(
191                        signal_type.clone(),
192                        format!("compiler_error: {}", content),
193                        0.9,
194                    ));
195                    break;
196                }
197            }
198
199            // Check warning patterns (lower confidence)
200            for (pattern, signal_type) in &self.rustc_warning_patterns {
201                if let Some(caps) = pattern.captures(line) {
202                    let content = caps.get(1).map(|m| m.as_str()).unwrap_or(line);
203                    signals.push(RuntimeSignal::new(
204                        signal_type.clone(),
205                        format!("compiler_warning: {}", content),
206                        0.6,
207                    ));
208                    break;
209                }
210            }
211        }
212
213        signals
214    }
215}
216
217impl Default for CompilerDiagnosticsParser {
218    fn default() -> Self {
219        Self::new()
220    }
221}
222
223/// Stack trace parser
224pub struct StackTraceParser {
225    /// Pattern for stack trace lines
226    stack_trace_pattern: Regex,
227    /// Pattern for panic messages
228    panic_pattern: Regex,
229}
230
231impl StackTraceParser {
232    /// Create a new parser
233    pub fn new() -> Self {
234        Self {
235            stack_trace_pattern: Regex::new(r"(?m)^\s+at\s+(.+?)(?:\s+in\s+(.+?))?(?:\s+\(.*\))?$")
236                .unwrap(),
237            panic_pattern: Regex::new(r"(?i)(thread\s+.*\s+panicked|panicked\s+at)").unwrap(),
238        }
239    }
240
241    /// Parse stack trace and extract signals
242    pub fn parse(&self, output: &str) -> Vec<RuntimeSignal> {
243        let mut signals = Vec::new();
244
245        // Check for panic
246        for line in output.lines() {
247            if self.panic_pattern.is_match(line) {
248                signals.push(RuntimeSignal::new(
249                    RuntimeSignalType::RuntimePanic,
250                    format!("panic: {}", line.trim()),
251                    0.95,
252                ));
253            }
254        }
255
256        // Extract stack frames
257        for cap in self.stack_trace_pattern.captures_iter(output) {
258            if let Some(location) = cap.get(1) {
259                let location_str = location.as_str().to_string();
260                let confidence = if location_str.contains("main") || location_str.contains("bin") {
261                    0.9
262                } else {
263                    0.7
264                };
265
266                signals.push(RuntimeSignal::new(
267                    RuntimeSignalType::RuntimePanic,
268                    format!("stack_frame: {}", location_str),
269                    confidence,
270                ));
271            }
272        }
273
274        signals
275    }
276}
277
278impl Default for StackTraceParser {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284/// Execution log analyzer
285pub struct LogAnalyzer {
286    /// Patterns for timeout errors
287    timeout_patterns: Vec<Regex>,
288    /// Patterns for resource errors
289    resource_patterns: Vec<Regex>,
290    /// Patterns for test failures
291    test_failure_patterns: Vec<Regex>,
292}
293
294impl LogAnalyzer {
295    /// Create a new analyzer
296    pub fn new() -> Self {
297        Self {
298            timeout_patterns: vec![
299                Regex::new(r"(?i)timeout").unwrap(),
300                Regex::new(r"(?i)timed out").unwrap(),
301                Regex::new(r"(?i)deadline exceeded").unwrap(),
302            ],
303            resource_patterns: vec![
304                Regex::new(r"(?i)(out of memory|oom)").unwrap(),
305                Regex::new(r"(?i)memory allocation failed").unwrap(),
306                Regex::new(r"(?i)disk (full|space)").unwrap(),
307                Regex::new(r"(?i)too many open files").unwrap(),
308                Regex::new(r"(?i)resource temporarily unavailable").unwrap(),
309            ],
310            test_failure_patterns: vec![
311                Regex::new(r"(?i)test failed").unwrap(),
312                Regex::new(r"(?i)assertion failed").unwrap(),
313                Regex::new(r"(?i)expected .* but got").unwrap(),
314                Regex::new(r"(?i)panicked at").unwrap(),
315            ],
316        }
317    }
318
319    /// Analyze logs and extract signals
320    pub fn analyze(&self, logs: &str) -> Vec<RuntimeSignal> {
321        let mut signals = Vec::new();
322
323        for line in logs.lines() {
324            // Check timeouts
325            for pattern in &self.timeout_patterns {
326                if pattern.is_match(line) {
327                    signals.push(RuntimeSignal::new(
328                        RuntimeSignalType::Timeout,
329                        format!("timeout: {}", line.trim()),
330                        0.85,
331                    ));
332                    break;
333                }
334            }
335
336            // Check resource issues
337            for pattern in &self.resource_patterns {
338                if pattern.is_match(line) {
339                    signals.push(RuntimeSignal::new(
340                        RuntimeSignalType::ResourceExhaustion,
341                        format!("resource: {}", line.trim()),
342                        0.9,
343                    ));
344                    break;
345                }
346            }
347
348            // Check test failures
349            for pattern in &self.test_failure_patterns {
350                if pattern.is_match(line) {
351                    signals.push(RuntimeSignal::new(
352                        RuntimeSignalType::TestFailure,
353                        format!("test_failure: {}", line.trim()),
354                        0.9,
355                    ));
356                    break;
357                }
358            }
359        }
360
361        signals.into_iter().collect()
362    }
363}
364
365impl Default for LogAnalyzer {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// Runtime signal extractor - combines all parsers
372pub struct RuntimeSignalExtractor {
373    compiler_parser: CompilerDiagnosticsParser,
374    stack_trace_parser: StackTraceParser,
375    log_analyzer: LogAnalyzer,
376}
377
378impl RuntimeSignalExtractor {
379    /// Create a new extractor
380    pub fn new() -> Self {
381        Self {
382            compiler_parser: CompilerDiagnosticsParser::new(),
383            stack_trace_parser: StackTraceParser::new(),
384            log_analyzer: LogAnalyzer::new(),
385        }
386    }
387
388    /// Extract signals from compiler output
389    pub fn extract_from_compiler(&self, output: &str) -> Vec<RuntimeSignal> {
390        self.compiler_parser.parse(output)
391    }
392
393    /// Extract signals from stack trace
394    pub fn extract_from_stack_trace(&self, output: &str) -> Vec<RuntimeSignal> {
395        self.stack_trace_parser.parse(output)
396    }
397
398    /// Extract signals from execution logs
399    pub fn extract_from_logs(&self, logs: &str) -> Vec<RuntimeSignal> {
400        self.log_analyzer.analyze(logs)
401    }
402
403    /// Extract signals from all sources
404    pub fn extract_all(
405        &self,
406        compiler_output: Option<&str>,
407        stack_trace: Option<&str>,
408        logs: Option<&str>,
409    ) -> Vec<RuntimeSignal> {
410        let mut all_signals = Vec::new();
411
412        if let Some(output) = compiler_output {
413            all_signals.extend(self.extract_from_compiler(output));
414        }
415
416        if let Some(trace) = stack_trace {
417            all_signals.extend(self.extract_from_stack_trace(trace));
418        }
419
420        if let Some(log) = logs {
421            all_signals.extend(self.extract_from_logs(log));
422        }
423
424        // Deduplicate by content
425        let mut seen = HashSet::new();
426        all_signals.retain(|s| seen.insert(s.content.clone()));
427
428        all_signals
429    }
430}
431
432impl Default for RuntimeSignalExtractor {
433    fn default() -> Self {
434        Self::new()
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    #[test]
443    fn test_compiler_error_parsing() {
444        let parser = CompilerDiagnosticsParser::new();
445
446        let output = r#"
447error[E0425]: cannot find function `foo` in this scope
448  --> src/main.rs:10:5
449   |
45010 |     foo();
451   |     ^^^ not found in this scope
452
453error[E0308]: mismatched types
454"#;
455
456        let signals = parser.parse(output);
457        assert!(!signals.is_empty());
458        assert!(signals
459            .iter()
460            .any(|s| s.content.contains("cannot find function")));
461    }
462
463    #[test]
464    fn test_stack_trace_parsing() {
465        let parser = StackTraceParser::new();
466
467        let trace = r#"
468thread 'main' panicked at 'something failed', src/main.rs:10:5
469stack backtrace:
470   0: <core::panic::unwind_safe>::{{closure}}
471   1: std::panicking::{{closure}}
472   2: main::foo
473"#;
474
475        let signals = parser.parse(trace);
476        assert!(!signals.is_empty());
477    }
478
479    #[test]
480    fn test_log_analysis() {
481        let analyzer = LogAnalyzer::new();
482
483        let logs = r#"
484[INFO] Starting application
485[ERROR] Connection timeout after 30s
486[ERROR] Test case 'test_foo' failed: assertion failed
487"#;
488
489        let signals = analyzer.analyze(logs);
490        assert!(!signals.is_empty());
491    }
492
493    #[test]
494    fn test_runtime_signal_extractor() {
495        let extractor = RuntimeSignalExtractor::new();
496
497        let signals = extractor.extract_all(
498            Some("error[E0425]: cannot find function"),
499            Some("thread 'main' panicked"),
500            Some("connection timeout"),
501        );
502
503        assert!(signals.len() >= 3);
504    }
505}