use std::fs; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::RwLock; use tracing::{error, info, warn}; /// Manages the ai.md self-improvement file. /// Loaded on startup and periodically refreshed. pub struct InstructionsManager { path: PathBuf, current: RwLock, } impl InstructionsManager { pub fn new(path: PathBuf) -> Self { let initial = fs::read_to_string(&path).unwrap_or_default(); if initial.is_empty() { warn!("ai.md is empty or missing at {:?}", path); } else { info!("Loaded ai.md ({} bytes)", initial.len()); } Self { path, current: RwLock::new(initial), } } /// Read the current instructions pub async fn get(&self) -> String { self.current.read().await.clone() } /// Append a new entry to the Improvement Log and optionally update the instructions. /// Returns the updated full content. pub async fn update(&self, entry: &str, new_content: Option<&str>) -> Result { if let Some(content) = new_content { // Full replacement fs::write(&self.path, content).map_err(|e| format!("Write failed: {}", e))?; let mut current = self.current.write().await; *current = content.to_string(); info!("ai.md fully replaced ({} bytes)", content.len()); Ok(content.to_string()) } else { // Append to Improvement Log only let mut current = self.current.write().await; let log_entry = format!("\n- {} — {}", chrono::Utc::now().format("%Y-%m-%d %H:%M"), entry); // Find the Improvement Log section and append if let Some(pos) = current.rfind("\n## Improvement Log") { // Find the next section after Improvement Log, or end of file let after_log = ¤t[pos..]; if let Some(section_start) = after_log[1..].find("\n## ") { let insert_at = pos + 1 + section_start; current.insert_str(insert_at, &format!("{}\n", log_entry)); } else { current.push_str(&format!("{}\n", log_entry)); } } else { current.push_str(&format!("\n## Improvement Log\n{}", log_entry)); } fs::write(&self.path, &*current).map_err(|e| format!("Write failed: {}", e))?; info!("ai.md improvement log appended: {}", entry); Ok(current.clone()) } } /// Reload from disk pub async fn reload(&self) { match fs::read_to_string(&self.path) { Ok(content) => { let len = content.len(); let mut current = self.current.write().await; *current = content; info!("ai.md reloaded from disk ({} bytes)", len); } Err(e) => error!("Failed to reload ai.md: {}", e), } } } /// Wrapper for thread-safe sharing pub type SharedInstructions = Arc; pub fn create_shared(path: PathBuf) -> SharedInstructions { Arc::new(InstructionsManager::new(path)) }