use sha2::{Digest, Sha256};
pub fn claude_store_root(
explicit: Option<&std::path::Path>,
env_dir: Option<&str>,
home: &std::path::Path,
) -> std::path::PathBuf {
if let Some(p) = explicit {
return p.to_path_buf();
}
if let Some(e) = env_dir.map(str::trim).filter(|e| !e.is_empty()) {
return std::path::PathBuf::from(e);
}
home.join(".claude")
}
pub fn claude_project_folder(abs_path: &str) -> String {
abs_path
.chars()
.map(|c| match c {
'/' | '.' | '_' => '-',
other => other,
})
.collect()
}
pub fn gemini_project_hash(abs_path: &str) -> String {
let digest = Sha256::digest(abs_path.as_bytes());
let mut s = String::with_capacity(digest.len() * 2);
for b in digest {
s.push_str(&format!("{b:02x}"));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn claude_folder_matches_the_observed_encoding() {
assert_eq!(
claude_project_folder("/home/dev/myproject"),
"-home-dev-myproject"
);
assert_eq!(
claude_project_folder("/home/dev/my_project"),
"-home-dev-my-project"
);
assert_eq!(
claude_project_folder("/home/dev/.config"),
"-home-dev--config"
);
assert_eq!(
claude_project_folder("/home/dev/a.b/c_d"),
"-home-dev-a-b-c-d"
);
}
#[test]
fn gemini_hash_is_sha256_of_the_path() {
assert_eq!(
gemini_project_hash("/home/dev/myproject"),
"5ea998fd0e431a6b5f864ca7d6386eacfa7b33d53df16df5f0faeb1c0cd2d021"
);
}
}
#[cfg(test)]
mod store_root_tests {
use super::*;
use std::path::{Path, PathBuf};
#[test]
fn an_explicit_config_dir_is_where_the_transcript_goes() {
let home = Path::new("/home/u");
let slot = PathBuf::from("/data/slots/abc");
assert_eq!(
claude_store_root(Some(&slot), Some("/env/dir"), home),
slot,
"an explicit target outranks the environment"
);
}
#[test]
fn the_environment_names_the_store_when_nothing_else_does() {
let home = Path::new("/home/u");
assert_eq!(
claude_store_root(None, Some("/env/dir"), home),
PathBuf::from("/env/dir")
);
}
#[test]
fn without_either_it_is_the_default_store() {
let home = Path::new("/home/u");
assert_eq!(
claude_store_root(None, None, home),
PathBuf::from("/home/u/.claude")
);
assert_eq!(
claude_store_root(None, Some(" "), home),
PathBuf::from("/home/u/.claude")
);
}
}