use std::collections::{BTreeSet, HashMap};
use std::path::Path;
use std::sync::OnceLock;
use uuid::Uuid;
fn registry() -> &'static std::sync::Mutex<HashMap<(Uuid, String), u64>> {
static REGISTRY: OnceLock<std::sync::Mutex<HashMap<(Uuid, String), u64>>> = OnceLock::new();
REGISTRY.get_or_init(|| std::sync::Mutex::new(HashMap::new()))
}
fn epochs() -> &'static std::sync::Mutex<HashMap<Uuid, u64>> {
static EPOCHS: OnceLock<std::sync::Mutex<HashMap<Uuid, u64>>> = OnceLock::new();
EPOCHS.get_or_init(|| std::sync::Mutex::new(HashMap::new()))
}
pub fn skill_slug_from_path(path: &Path) -> Option<String> {
let file_name = path.file_name()?.to_str()?;
if file_name != "SKILL.md" {
return None;
}
let mut comps = path.components().rev();
comps.next()?; let slug = comps.next()?;
if comps.next()?.as_os_str() != "skills" {
return None;
}
slug.as_os_str().to_str().map(|s| s.to_string())
}
pub fn mark_seen(session_id: Uuid, slug: &str) {
let epoch = current_epoch(session_id);
registry()
.lock()
.expect("seen_skills registry poisoned")
.insert((session_id, slug.to_string()), epoch);
}
fn current_epoch(session_id: Uuid) -> u64 {
*epochs()
.lock()
.expect("seen_skills epochs poisoned")
.get(&session_id)
.unwrap_or(&0)
}
pub fn note_compaction(session_id: Uuid) {
let mut epochs = epochs().lock().expect("seen_skills epochs poisoned");
let next = epochs.get(&session_id).copied().unwrap_or(0) + 1;
epochs.insert(session_id, next);
}
pub fn seen_since_compaction(session_id: Uuid, slug: &str) -> bool {
let stored = registry()
.lock()
.expect("seen_skills registry poisoned")
.get(&(session_id, slug.to_string()))
.copied();
if let Some(epoch) = stored {
epoch >= current_epoch(session_id)
} else {
false
}
}
pub fn was_seen(session_id: Uuid, slug: &str) -> bool {
registry()
.lock()
.expect("seen_skills registry poisoned")
.contains_key(&(session_id, slug.to_string()))
}
pub fn seen_for_session(session_id: Uuid) -> Vec<String> {
let all: BTreeSet<String> = registry()
.lock()
.expect("seen_skills registry poisoned")
.iter()
.filter(|((sid, _), _)| *sid == session_id)
.map(|((_, slug), _)| slug.clone())
.collect();
all.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slug_extraction_from_skill_paths() {
assert_eq!(
skill_slug_from_path(Path::new(
"/root/.opencrabs/profiles/ops/skills/opencrabs-dev/SKILL.md"
)),
Some("opencrabs-dev".to_string())
);
assert_eq!(
skill_slug_from_path(Path::new("skills/foo/SKILL.md")),
Some("foo".to_string())
);
}
#[test]
fn non_skill_paths_yield_none() {
assert_eq!(
skill_slug_from_path(Path::new("/home/user/MEMORY.md")),
None
);
assert_eq!(skill_slug_from_path(Path::new("skills/foo/other.md")), None);
assert_eq!(
skill_slug_from_path(Path::new("not-skills/foo/SKILL.md")),
None
);
assert_eq!(skill_slug_from_path(Path::new("skills/foo/")), None);
}
#[test]
fn mark_seen_is_idempotent_and_session_scoped() {
let a = Uuid::new_v4();
let b = Uuid::new_v4();
mark_seen(a, "opencrabs-dev");
mark_seen(a, "opencrabs-dev");
assert!(was_seen(a, "opencrabs-dev"));
assert_eq!(seen_for_session(a), vec!["opencrabs-dev".to_string()]);
assert!(!was_seen(b, "opencrabs-dev"));
assert!(seen_for_session(b).is_empty());
}
#[test]
fn seen_list_is_sorted_and_multi() {
let a = Uuid::new_v4();
mark_seen(a, "zeta");
mark_seen(a, "alpha");
assert_eq!(
seen_for_session(a),
vec!["alpha".to_string(), "zeta".to_string()]
);
}
#[test]
fn fresh_session_reports_not_seen_since_compaction() {
let a = Uuid::new_v4();
assert!(!seen_since_compaction(a, "anything"));
}
#[test]
fn seen_passes_and_compaction_rearms_gate() {
let a = Uuid::new_v4();
mark_seen(a, "my-skill");
assert!(seen_since_compaction(a, "my-skill"));
note_compaction(a);
assert!(!seen_since_compaction(a, "my-skill"));
mark_seen(a, "my-skill");
assert!(seen_since_compaction(a, "my-skill"));
}
#[test]
fn compaction_clears_nothing_stamp_inventory_intact() {
let a = Uuid::new_v4();
mark_seen(a, "one");
mark_seen(a, "two");
note_compaction(a);
assert_eq!(
seen_for_session(a),
vec!["one".to_string(), "two".to_string()]
);
assert!(!seen_since_compaction(a, "one"));
assert!(!seen_since_compaction(a, "two"));
}
#[test]
fn sessions_are_independent() {
let a = Uuid::new_v4();
let b = Uuid::new_v4();
mark_seen(a, "sk");
note_compaction(a);
mark_seen(b, "sk");
assert!(seen_since_compaction(b, "sk"));
assert!(!seen_since_compaction(a, "sk"));
}
}