use std::path::{Component, Path, PathBuf};
fn repo_root() -> PathBuf {
std::env::var("YANA_REPO_ROOT")
.map(PathBuf::from)
.ok()
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| PathBuf::from("."))
}
fn repo_relative(raw: &str) -> String {
let folded = lexical_fold(Path::new(raw));
if folded.is_absolute() {
let root = lexical_fold(&repo_root());
if let Ok(stripped) = folded.strip_prefix(&root) {
return stripped.to_string_lossy().into_owned();
}
return folded.to_string_lossy().into_owned();
}
folded.to_string_lossy().into_owned()
}
fn lexical_fold(p: &Path) -> PathBuf {
let mut out = PathBuf::new();
for c in p.components() {
match c {
Component::ParentDir => {
out.pop();
}
Component::CurDir => {}
other => out.push(other.as_os_str()),
}
}
out
}
pub fn protected_hit(raw: &str, protected: &[String]) -> Option<String> {
let rel = repo_relative(raw);
for p in protected {
if matches_prefix(&rel, p) || matches_prefix(raw, p) {
return Some(p.clone());
}
}
None
}
fn matches_prefix(candidate: &str, prefix: &str) -> bool {
candidate == prefix || candidate.starts_with(&format!("{prefix}/"))
}
#[cfg(test)]
mod tests {
use super::*;
fn prot() -> Vec<String> {
vec!["core/rules".to_string(), ".git".to_string()]
}
#[test]
fn relative_path_is_protected() {
assert!(protected_hit("core/rules/00-meta.md", &prot()).is_some());
}
#[test]
fn dotdot_path_is_protected() {
assert!(protected_hit("core/../core/rules/x.md", &prot()).is_some());
}
#[test]
fn absolute_path_under_repo_root_is_protected() {
std::env::set_var("YANA_REPO_ROOT", "/workspaces/Yana-AI");
let hit = protected_hit("/workspaces/Yana-AI/core/rules/00-meta.md", &prot());
std::env::remove_var("YANA_REPO_ROOT");
assert!(hit.is_some(), "absolute path under repo root must be blocked");
}
#[test]
fn sibling_dir_is_not_protected() {
assert!(protected_hit("core/rulesets-public/x", &prot()).is_none());
}
#[test]
fn unrelated_absolute_path_not_treated_as_repo_relative() {
std::env::set_var("YANA_REPO_ROOT", "/workspaces/Yana-AI");
let hit = protected_hit("/etc/passwd", &prot());
std::env::remove_var("YANA_REPO_ROOT");
assert!(hit.is_none());
}
}