86 lines
3.1 KiB
Rust
86 lines
3.1 KiB
Rust
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<String>,
|
|
}
|
|
|
|
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<String, String> {
|
|
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<InstructionsManager>;
|
|
|
|
pub fn create_shared(path: PathBuf) -> SharedInstructions {
|
|
Arc::new(InstructionsManager::new(path))
|
|
}
|