use std::fs;
use std::path::{Path, PathBuf};
pub fn get_memory_base_dir() -> PathBuf {
if let Ok(dir) = std::env::var("AI_REMOTE_MEMORY_DIR") {
return PathBuf::from(dir);
}
if let Some(home) = dirs::home_dir() {
home.join(".ai")
} else {
std::env::current_dir()
.map(|p| p.join(".ai"))
.unwrap_or_else(|_| PathBuf::from(".ai"))
}
}
pub fn get_auto_mem_path() -> PathBuf {
if let Ok(override_path) = std::env::var("AI_MEMORY_PATH_OVERRIDE") {
let path = PathBuf::from(&override_path);
if is_valid_memory_path(&path) {
return path;
}
}
let base = get_memory_base_dir();
let projects_dir = base.join("projects");
let project_slug = get_project_slug();
projects_dir.join(project_slug).join("memory")
}
pub fn get_project_slug() -> String {
if let Ok(cwd) = std::env::current_dir() {
let name = cwd.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
sanitize_path_component(name)
} else {
"unknown".to_string()
}
}
fn sanitize_path_component(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' {
c
} else {
'_'
}
})
.collect()
}
fn is_valid_memory_path(path: &Path) -> bool {
if !path.is_absolute() {
return false;
}
let path_str = path.to_string_lossy();
if path_str.contains('\0') {
return false;
}
if path_str.len() < 3 {
return false;
}
true
}
pub fn get_memory_entrypoint() -> PathBuf {
get_auto_mem_path().join("MEMORY.md")
}
pub fn is_auto_memory_enabled() -> bool {
if let Ok(val) = std::env::var("AI_DISABLE_AUTO_MEMORY") {
if val == "1" || val.to_lowercase() == "true" {
return false;
}
}
if let Ok(val) = std::env::var("AI_SIMPLE") {
if val == "1" || val.to_lowercase() == "true" {
return false;
}
}
true
}
pub fn ensure_memory_dir_exists() -> std::io::Result<()> {
let path = get_auto_mem_path();
fs::create_dir_all(path)?;
Ok(())
}
pub fn is_auto_mem_path(absolute_path: &Path) -> bool {
let mem_path = get_auto_mem_path();
absolute_path.starts_with(&mem_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_path_component() {
assert_eq!(sanitize_path_component("my-project"), "my-project");
assert_eq!(sanitize_path_component("My Project!"), "My_Project_");
assert_eq!(sanitize_path_component("123-test"), "123-test");
}
#[test]
fn test_get_project_slug() {
let slug = get_project_slug();
assert!(!slug.is_empty());
}
}