use crate::edit_control::ModifiableEdit;
use crate::classification::EditClassificationSystem;
use crate::classification::types::EditCategory;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Debug)]
pub struct ConditionalLogicSystem {
conditions: HashMap<String, Condition>,
chains: Vec<ConditionalChain>,
test_gate_system: TestGateSystem,
execution_history: Vec<ExecutionRecord>,
active_sessions: HashMap<String, ConditionalSession>,
}
impl ConditionalLogicSystem {
pub fn new() -> Self {
Self {
conditions: HashMap::new(),
chains: Vec::new(),
test_gate_system: TestGateSystem::new(),
execution_history: Vec::new(),
active_sessions: HashMap::new(),
}
}
pub fn create_session(&mut self, name: String) -> String {
let session_id = format!("session_{}", chrono::Utc::now().timestamp());
let session = ConditionalSession::new(session_id.clone(), name);
self.active_sessions.insert(session_id.clone(), session);
session_id
}
pub fn add_condition(&mut self, condition: Condition) -> Result<String, String> {
let condition_id = condition.id.clone();
if condition.condition_type == ConditionType::Custom && condition.custom_script.is_none() {
return Err("Custom conditions require a custom script".to_string());
}
self.conditions.insert(condition_id.clone(), condition);
Ok(condition_id)
}
pub fn create_chain(&mut self, chain: ConditionalChain) -> Result<String, String> {
for link in &chain.links {
if !self.conditions.contains_key(&link.condition_id) {
return Err(format!("Condition {} not found", link.condition_id));
}
}
let chain_id = chain.id.clone();
self.chains.push(chain);
Ok(chain_id)
}
pub fn apply_conditionally(
&mut self,
edit: &ModifiableEdit,
conditions: &[String],
session_id: Option<String>
) -> Result<ConditionalApplicationResult, String> {
let start_time = std::time::Instant::now();
let edit_id = self.generate_edit_id(edit);
let context = ExecutionContext::new(edit.clone(), session_id.clone());
let mut condition_results = Vec::new();
for condition_id in conditions {
if let Some(condition) = self.conditions.get(condition_id).cloned() {
let result = self.evaluate_condition(&condition, edit, &context)?;
condition_results.push((condition_id.clone(), result.clone()));
if !result.passed {
return Ok(ConditionalApplicationResult {
success: false,
applied: false,
condition_results,
execution_time: start_time.elapsed(),
failure_reason: Some(result.message),
edit_id: edit_id.clone(),
});
}
} else {
return Err(format!("Condition {} not found", condition_id));
}
}
let application_result = self.apply_edit_safely(edit, &context)?;
let execution_record = ExecutionRecord {
edit_id: edit_id.clone(),
session_id: session_id.clone(),
conditions_evaluated: conditions.to_vec(),
condition_results: condition_results.clone(),
applied: application_result,
timestamp: chrono::Utc::now(),
execution_time: start_time.elapsed(),
};
self.execution_history.push(execution_record);
Ok(ConditionalApplicationResult {
success: true,
applied: application_result,
condition_results,
execution_time: start_time.elapsed(),
failure_reason: None,
edit_id,
})
}
pub fn execute_chain(
&mut self,
chain_id: &str,
session_id: Option<String>
) -> Result<ChainExecutionResult, String> {
let chain = self.chains.iter()
.find(|c| c.id == chain_id)
.ok_or_else(|| format!("Chain {} not found", chain_id))?
.clone();
let mut results = Vec::new();
for link in &chain.links {
let condition = self.conditions.get(&link.condition_id)
.ok_or_else(|| format!("Condition {} not found", link.condition_id))?
.clone();
let mut link_results = Vec::new();
for edit in &link.edits {
let exec_context = ExecutionContext::new(edit.clone(), session_id.clone());
let condition_result = self.evaluate_condition(&condition, edit, &exec_context)?;
let edit_id = self.generate_edit_id(edit);
if condition_result.passed {
let applied = self.apply_edit_safely(edit, &exec_context)?;
link_results.push(LinkExecutionResult {
edit_id,
condition_passed: true,
applied,
message: condition_result.message,
});
} else {
link_results.push(LinkExecutionResult {
edit_id,
condition_passed: false,
applied: false,
message: condition_result.message,
});
match link.failure_strategy {
ChainFailureStrategy::StopChain => {
return Ok(ChainExecutionResult {
chain_id: chain_id.to_string(),
completed: false,
link_results: vec![(link.id.clone(), link_results)],
total_edits_applied: results.iter().map(|(_, r): &(String, Vec<LinkExecutionResult>)| r.len()).sum(),
failure_reason: Some(format!("Chain stopped at link {} due to condition failure", link.id)),
});
}
ChainFailureStrategy::SkipLink => {
break; }
ChainFailureStrategy::Continue => {
}
}
}
}
results.push((link.id.clone(), link_results));
}
Ok(ChainExecutionResult {
chain_id: chain_id.to_string(),
completed: true,
link_results: results.clone(),
total_edits_applied: results.iter().map(|(_, r): &(String, Vec<LinkExecutionResult>)| r.iter().filter(|lr| lr.applied).count()).sum(),
failure_reason: None,
})
}
fn generate_edit_id(&self, edit: &ModifiableEdit) -> String {
format!("{}:{}:{}",
edit.base_edit.file,
edit.base_edit.line_range.0,
edit.base_edit.line_range.1
)
}
fn evaluate_condition(
&mut self,
condition: &Condition,
edit: &ModifiableEdit,
context: &ExecutionContext
) -> Result<ConditionResult, String> {
match &condition.condition_type {
ConditionType::TestPassing => {
self.test_gate_system.evaluate_tests(&condition.test_config, edit, context)
}
ConditionType::ClassificationBased => {
self.evaluate_classification_condition(&condition.classification_criteria, edit)
}
ConditionType::AlwaysPass => {
Ok(ConditionResult {
passed: true,
message: "Always pass condition".to_string(),
details: None,
})
}
_ => {
Ok(ConditionResult {
passed: true,
message: "Condition type not implemented yet".to_string(),
details: None,
})
}
}
}
fn evaluate_classification_condition(
&self,
criteria: &Option<ClassificationCriteria>,
edit: &ModifiableEdit
) -> Result<ConditionResult, String> {
let criteria = criteria.as_ref()
.ok_or_else(|| "Classification criteria required".to_string())?;
let classifier = EditClassificationSystem::new();
let classification = classifier.classify_edit(edit)
.map_err(|e| format!("Classification failed: {}", e))?;
let passed = match criteria {
ClassificationCriteria::MinConfidence(threshold) => {
classification.classification_confidence >= *threshold
}
ClassificationCriteria::RequiredCategory(category) => {
classification.category == *category
}
ClassificationCriteria::MaxRiskScore(max_risk) => {
classification.risk_assessment.overall_score <= *max_risk
}
};
Ok(ConditionResult {
passed,
message: if passed {
format!("Classification criteria met: {:?}", criteria)
} else {
format!("Classification criteria not met: {:?}", criteria)
},
details: Some(format!("Category: {:?}, Confidence: {:.2}, Risk: {:.2}",
classification.category,
classification.classification_confidence,
classification.risk_assessment.overall_score
)),
})
}
fn apply_edit_safely(
&self,
edit: &ModifiableEdit,
_context: &ExecutionContext
) -> Result<bool, String> {
if edit.compute_final_code().is_empty() {
return Ok(false);
}
Ok(true)
}
pub fn get_execution_history(&self) -> &[ExecutionRecord] {
&self.execution_history
}
pub fn get_active_sessions(&self) -> &HashMap<String, ConditionalSession> {
&self.active_sessions
}
pub fn get_condition_success_rate(&self, condition_id: &str) -> f64 {
let total = self.execution_history.iter()
.filter(|record| record.conditions_evaluated.contains(&condition_id.to_string()))
.count();
if total == 0 {
return 0.0;
}
let successful = self.execution_history.iter()
.filter(|record| {
record.conditions_evaluated.contains(&condition_id.to_string()) &&
record.condition_results.iter()
.any(|(id, result)| id == condition_id && result.passed)
})
.count();
successful as f64 / total as f64
}
}
#[derive(Debug, Clone)]
pub struct Condition {
pub id: String,
pub name: String,
pub description: String,
pub condition_type: ConditionType,
pub test_config: Option<TestConfiguration>,
pub classification_criteria: Option<ClassificationCriteria>,
pub custom_script: Option<String>,
pub timeout_seconds: u64,
}
impl Condition {
pub fn new_test_condition(id: String, name: String, test_config: TestConfiguration) -> Self {
Self {
id,
name,
description: "Test-based condition".to_string(),
condition_type: ConditionType::TestPassing,
test_config: Some(test_config),
classification_criteria: None,
custom_script: None,
timeout_seconds: 300, }
}
pub fn new_classification_condition(
id: String,
name: String,
criteria: ClassificationCriteria
) -> Self {
Self {
id,
name,
description: "Classification-based condition".to_string(),
condition_type: ConditionType::ClassificationBased,
test_config: None,
classification_criteria: Some(criteria),
custom_script: None,
timeout_seconds: 60,
}
}
pub fn new_always_pass(id: String, name: String) -> Self {
Self {
id,
name,
description: "Always pass condition for testing".to_string(),
condition_type: ConditionType::AlwaysPass,
test_config: None,
classification_criteria: None,
custom_script: None,
timeout_seconds: 1,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConditionType {
TestPassing,
ClassificationBased,
AlwaysPass, Custom,
}
#[derive(Debug, Clone)]
pub enum ClassificationCriteria {
MinConfidence(f64),
RequiredCategory(EditCategory),
MaxRiskScore(f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfiguration {
pub test_command: String,
pub test_args: Vec<String>,
pub working_directory: Option<String>,
pub required_exit_code: i32,
pub timeout_seconds: u64,
pub required_patterns: Vec<String>, pub forbidden_patterns: Vec<String>, }
impl TestConfiguration {
pub fn new(test_command: String) -> Self {
Self {
test_command,
test_args: Vec::new(),
working_directory: None,
required_exit_code: 0,
timeout_seconds: 300,
required_patterns: Vec::new(),
forbidden_patterns: Vec::new(),
}
}
pub fn cargo_test() -> Self {
Self::new("cargo".to_string())
.with_args(vec!["test".to_string()])
.with_timeout(600) }
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.test_args = args;
self
}
pub fn with_timeout(mut self, seconds: u64) -> Self {
self.timeout_seconds = seconds;
self
}
pub fn with_required_pattern(mut self, pattern: String) -> Self {
self.required_patterns.push(pattern);
self
}
}
#[derive(Debug, Clone)]
pub struct ConditionalChain {
pub id: String,
pub name: String,
pub description: String,
pub links: Vec<ChainLink>,
pub failure_strategy: ChainFailureStrategy,
}
impl ConditionalChain {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
description: String::new(),
links: Vec::new(),
failure_strategy: ChainFailureStrategy::StopChain,
}
}
pub fn add_link(&mut self, link: ChainLink) {
self.links.push(link);
}
}
#[derive(Debug, Clone)]
pub struct ChainLink {
pub id: String,
pub condition_id: String,
pub edits: Vec<ModifiableEdit>,
pub failure_strategy: ChainFailureStrategy,
}
#[derive(Debug, Clone)]
pub enum ChainFailureStrategy {
StopChain, SkipLink, Continue, }
#[derive(Debug)]
pub struct TestGateSystem {
active_tests: HashMap<String, TestExecution>,
}
impl TestGateSystem {
pub fn new() -> Self {
Self {
active_tests: HashMap::new(),
}
}
pub fn evaluate_tests(
&mut self,
config: &Option<TestConfiguration>,
_edit: &ModifiableEdit,
_context: &ExecutionContext
) -> Result<ConditionResult, String> {
let test_config = config.as_ref()
.ok_or_else(|| "Test configuration required for test conditions".to_string())?;
let execution_id = format!("test_{}", chrono::Utc::now().timestamp());
let start_time = std::time::Instant::now();
let mut command = std::process::Command::new(&test_config.test_command);
command.args(&test_config.test_args);
if let Some(ref workdir) = test_config.working_directory {
command.current_dir(workdir);
}
command.stdout(std::process::Stdio::piped());
command.stderr(std::process::Stdio::piped());
let output = command.output()
.map_err(|e| format!("Failed to execute test command: {}", e))?;
let execution_time = start_time.elapsed();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let exit_code_passed = output.status.code().unwrap_or(-1) == test_config.required_exit_code;
let patterns_passed = test_config.required_patterns.iter()
.all(|pattern| stdout.contains(pattern) || stderr.contains(pattern));
let no_forbidden_patterns = test_config.forbidden_patterns.iter()
.all(|pattern| !stdout.contains(pattern) && !stderr.contains(pattern));
let timeout_passed = execution_time.as_secs() <= test_config.timeout_seconds;
let passed = exit_code_passed && patterns_passed && no_forbidden_patterns && timeout_passed;
let test_execution = TestExecution {
id: execution_id.clone(),
config: test_config.clone(),
exit_code: output.status.code().unwrap_or(-1),
stdout: stdout.to_string(),
stderr: stderr.to_string(),
execution_time,
passed,
};
self.active_tests.insert(execution_id, test_execution);
Ok(ConditionResult {
passed,
message: if passed {
"All tests passed successfully".to_string()
} else {
format!(
"Tests failed - Exit code: {}, Patterns passed: {}, No forbidden patterns: {}, Timeout passed: {}",
exit_code_passed, patterns_passed, no_forbidden_patterns, timeout_passed
)
},
details: Some(format!("stdout: {}\nstderr: {}", stdout, stderr)),
})
}
}
#[derive(Debug, Clone)]
pub struct ConditionResult {
pub passed: bool,
pub message: String,
pub details: Option<String>,
}
#[derive(Debug)]
pub struct ConditionalApplicationResult {
pub success: bool,
pub applied: bool,
pub condition_results: Vec<(String, ConditionResult)>,
pub execution_time: std::time::Duration,
pub failure_reason: Option<String>,
pub edit_id: String,
}
#[derive(Debug)]
pub struct ChainExecutionResult {
pub chain_id: String,
pub completed: bool,
pub link_results: Vec<(String, Vec<LinkExecutionResult>)>,
pub total_edits_applied: usize,
pub failure_reason: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LinkExecutionResult {
pub edit_id: String,
pub condition_passed: bool,
pub applied: bool,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct ExecutionContext {
pub edit: ModifiableEdit,
pub session_id: Option<String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl ExecutionContext {
pub fn new(edit: ModifiableEdit, session_id: Option<String>) -> Self {
Self {
edit,
session_id,
timestamp: chrono::Utc::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct ConditionalSession {
pub id: String,
pub name: String,
pub created_at: chrono::DateTime<chrono::Utc>,
pub edits_applied: Vec<String>,
pub conditions_used: Vec<String>,
pub chains_executed: Vec<String>,
}
impl ConditionalSession {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
created_at: chrono::Utc::now(),
edits_applied: Vec::new(),
conditions_used: Vec::new(),
chains_executed: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ExecutionRecord {
pub edit_id: String,
pub session_id: Option<String>,
pub conditions_evaluated: Vec<String>,
pub condition_results: Vec<(String, ConditionResult)>,
pub applied: bool,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub execution_time: std::time::Duration,
}
#[derive(Debug, Clone)]
pub struct TestExecution {
pub id: String,
pub config: TestConfiguration,
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub execution_time: std::time::Duration,
pub passed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::gpt4_agent::ProposedEdit;
use crate::classification::types::{SafeType, ConfidenceLevel};
fn create_test_edit() -> ModifiableEdit {
let proposed_edit = ProposedEdit {
file: "test.rs".to_string(),
line_range: (10, 15),
new_code: "fn test() { println!(\"Hello\"); }".to_string(),
reason: "Test edit".to_string(),
confidence: 0.9,
};
ModifiableEdit::from_proposed_edit(proposed_edit)
}
#[test]
fn test_conditional_system_creation() {
let system = ConditionalLogicSystem::new();
assert_eq!(system.conditions.len(), 0);
assert_eq!(system.chains.len(), 0);
}
#[test]
fn test_session_creation() {
let mut system = ConditionalLogicSystem::new();
let session_id = system.create_session("test_session".to_string());
assert!(system.active_sessions.contains_key(&session_id));
assert_eq!(system.active_sessions[&session_id].name, "test_session");
}
#[test]
fn test_condition_creation() {
let mut system = ConditionalLogicSystem::new();
let test_config = TestConfiguration::cargo_test();
let condition = Condition::new_test_condition(
"test_condition".to_string(),
"Test Condition".to_string(),
test_config
);
let condition_id = system.add_condition(condition).unwrap();
assert_eq!(condition_id, "test_condition");
assert!(system.conditions.contains_key(&condition_id));
}
#[test]
fn test_classification_condition() {
let mut system = ConditionalLogicSystem::new();
let condition = Condition::new_classification_condition(
"safe_edits_only".to_string(),
"Safe Edits Only".to_string(),
ClassificationCriteria::RequiredCategory(EditCategory::Safe {
subcategory: SafeType::Documentation,
confidence_level: ConfidenceLevel::High,
})
);
let condition_id = system.add_condition(condition).unwrap();
assert!(system.conditions.contains_key(&condition_id));
}
#[test]
fn test_always_pass_condition() {
let mut system = ConditionalLogicSystem::new();
let edit = create_test_edit();
let condition = Condition::new_always_pass(
"always_pass".to_string(),
"Always Pass".to_string()
);
let condition_id = system.add_condition(condition).unwrap();
let result = system.apply_conditionally(
&edit,
&[condition_id],
None
).unwrap();
assert!(result.success);
assert!(result.applied);
assert_eq!(result.condition_results.len(), 1);
assert!(result.condition_results[0].1.passed);
}
#[test]
fn test_chain_creation() {
let mut system = ConditionalLogicSystem::new();
let condition = Condition::new_always_pass(
"test_condition".to_string(),
"Test Condition".to_string()
);
system.add_condition(condition).unwrap();
let mut chain = ConditionalChain::new(
"test_chain".to_string(),
"Test Chain".to_string()
);
chain.add_link(ChainLink {
id: "link1".to_string(),
condition_id: "test_condition".to_string(),
edits: Vec::new(),
failure_strategy: ChainFailureStrategy::StopChain,
});
let chain_id = system.create_chain(chain).unwrap();
assert_eq!(chain_id, "test_chain");
assert_eq!(system.chains.len(), 1);
}
#[test]
fn test_test_configuration() {
let config = TestConfiguration::cargo_test()
.with_timeout(300)
.with_required_pattern("test result: ok".to_string());
assert_eq!(config.test_command, "cargo");
assert_eq!(config.test_args, vec!["test"]);
assert_eq!(config.timeout_seconds, 300);
assert_eq!(config.required_patterns, vec!["test result: ok"]);
}
#[test]
fn test_success_rate_calculation() {
let system = ConditionalLogicSystem::new();
let rate = system.get_condition_success_rate("nonexistent");
assert_eq!(rate, 0.0);
}
#[test]
fn test_edit_id_generation() {
let system = ConditionalLogicSystem::new();
let edit = create_test_edit();
let edit_id = system.generate_edit_id(&edit);
assert_eq!(edit_id, "test.rs:10:15");
}
}