use std::collections::HashMap;
use std::path::Path;
use std::sync::{Mutex, OnceLock};
use uuid::Uuid;
type Seen = HashMap<(Uuid, String), u64>;
fn seen() -> &'static Mutex<Seen> {
static SEEN: OnceLock<Mutex<Seen>> = OnceLock::new();
SEEN.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn hash_content(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut h = DefaultHasher::new();
content.hash(&mut h);
h.finish()
}
fn key(session_id: Uuid, path: &Path) -> (Uuid, String) {
let resolved = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
(session_id, resolved.to_string_lossy().into_owned())
}
pub fn record(session_id: Uuid, path: &Path, content: &str) {
if let Ok(mut map) = seen().lock() {
map.insert(key(session_id, path), hash_content(content));
}
}
pub fn is_stale_write(session_id: Uuid, path: &Path, on_disk: Option<&str>) -> bool {
let Some(current) = on_disk else {
return false; };
let Ok(map) = seen().lock() else {
return false;
};
match map.get(&key(session_id, path)) {
Some(recorded) => *recorded != hash_content(current),
None => false,
}
}
pub fn refusal_message(path: &Path) -> String {
format!(
"Refusing to overwrite {}: it changed on disk after you read it. \
Another agent is working in this directory. Read the file again, \
re-apply your change to the current content, then write. \
If you only need to change part of it, prefer edit_file — it \
re-reads the file itself and cannot clobber a concurrent edit.",
path.display()
)
}
pub fn forget_session(session_id: Uuid) {
if let Ok(mut map) = seen().lock() {
map.retain(|(sid, _), _| *sid != session_id);
}
}