mod entry;
mod log;
mod logger;
mod replay;
pub use entry::{MutationRecord, TxAction, TxEntry};
pub use log::{TxLog, TxSummary};
pub use logger::TxLogger;
pub use replay::{ReplayAnalysis, ReplayEngine, ReplayError, ReplayMode, ReplayResult};
#[cfg(test)]
mod tests {
use super::*;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;
#[test]
fn test_basic_logging_workflow() {
let logger = TxLogger::start(PathBuf::from("/test/project"), 50);
logger.log_goal("test goal", "test", 0.9);
logger.log_mutation("Rename", "foo → bar", 3);
logger.log_file_modified(Path::new("test.rs"), 2);
thread::sleep(Duration::from_millis(10));
let log = logger.finish();
assert!(log.len() >= 4);
let summary = log.summary();
assert_eq!(summary.total_mutations, 1);
assert_eq!(summary.files_modified, 1);
}
#[test]
fn test_json_serialization() {
let mut log = TxLog::new();
log.log(TxAction::GoalSet {
query: "test query".to_string(),
intent_type: "refactoring".to_string(),
confidence: 0.95,
});
log.log(TxAction::MutationApplied {
mutation_type: "Rename".to_string(),
target: "old → new".to_string(),
changes: 5,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
let json = log.to_json().unwrap();
let loaded = TxLog::from_json(&json).unwrap();
assert_eq!(loaded.len(), log.len());
}
#[test]
fn test_checkpoint_based_filtering() {
let mut log = TxLog::new();
log.log(TxAction::GoalSet {
query: "goal 1".to_string(),
intent_type: "test".to_string(),
confidence: 0.8,
});
log.log(TxAction::Checkpoint {
name: "cp1".to_string(),
});
log.log(TxAction::GoalSet {
query: "goal 2".to_string(),
intent_type: "test".to_string(),
confidence: 0.9,
});
log.log(TxAction::MutationApplied {
mutation_type: "Rename".to_string(),
target: "foo".to_string(),
changes: 1,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
let entries = log.entries_since_checkpoint("cp1");
assert_eq!(entries.len(), 2); }
#[test]
fn test_replay_iterator() {
let mut log = TxLog::new();
log.log(TxAction::SessionStart {
project_path: PathBuf::from("/test"),
file_count: 10,
});
log.log(TxAction::GoalSet {
query: "test".to_string(),
intent_type: "test".to_string(),
confidence: 0.9,
});
log.log(TxAction::SessionEnd {
total_changes: 5,
files_modified: 2,
});
let entries: Vec<_> = log.iter().collect();
assert_eq!(entries.len(), 3);
assert!(matches!(entries[0].action, TxAction::SessionStart { .. }));
assert!(matches!(entries[1].action, TxAction::GoalSet { .. }));
assert!(matches!(entries[2].action, TxAction::SessionEnd { .. }));
}
#[test]
fn test_filter_by_action_type() {
let mut log = TxLog::new();
log.log(TxAction::GoalSet {
query: "goal 1".to_string(),
intent_type: "test".to_string(),
confidence: 0.8,
});
log.log(TxAction::MutationApplied {
mutation_type: "Rename".to_string(),
target: "a → b".to_string(),
changes: 1,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
log.log(TxAction::GoalSet {
query: "goal 2".to_string(),
intent_type: "test".to_string(),
confidence: 0.9,
});
log.log(TxAction::MutationApplied {
mutation_type: "Delete".to_string(),
target: "c".to_string(),
changes: 1,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
let mutations: Vec<_> = log
.iter()
.filter(|e| matches!(e.action, TxAction::MutationApplied { .. }))
.collect();
assert_eq!(mutations.len(), 2);
let goals: Vec<_> = log
.iter()
.filter(|e| matches!(e.action, TxAction::GoalSet { .. }))
.collect();
assert_eq!(goals.len(), 2);
}
#[test]
fn test_summary_generation() {
let mut log = TxLog::new();
log.log(TxAction::MutationApplied {
mutation_type: "Rename".to_string(),
target: "foo".to_string(),
changes: 3,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
log.log(TxAction::MutationApplied {
mutation_type: "AddField".to_string(),
target: "bar".to_string(),
changes: 1,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
log.log(TxAction::FileModified {
path: PathBuf::from("test.rs"),
changes: 2,
});
log.log(TxAction::Checkpoint {
name: "done".to_string(),
});
let summary = log.summary();
assert_eq!(summary.total_mutations, 2);
assert_eq!(summary.total_changes, 6); assert_eq!(summary.files_modified, 1);
assert!(summary.checkpoints.contains(&"done".to_string()));
}
}