use std::path::Path;
#[derive(Debug, Clone)]
pub struct FilePattern {
pub root_dirs: &'static [&'static str],
pub extensions: &'static [&'static str],
pub file_names: &'static [&'static str],
}
impl FilePattern {
pub fn matches(&self, path: &Path) -> bool {
if let Some(file_name) = path.file_name().and_then(|n| n.to_str())
&& self.file_names.contains(&file_name)
{
return true;
}
if !self.extensions.is_empty() {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
return self.extensions.contains(&ext);
}
return false;
}
true
}
pub fn is_in_root_dir(&self, path: &Path, base: &Path) -> bool {
if self.root_dirs.is_empty() {
return true;
}
for root in self.root_dirs {
let root_path = base.join(root);
if path.starts_with(&root_path) {
return true;
}
}
false
}
}
pub static SKILL_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude", "scripts"],
extensions: &["md"],
file_names: &["SKILL.md", "CLAUDE.md"],
};
pub static COMMAND_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude/commands", "commands"],
extensions: &["md"],
file_names: &[],
};
pub static MCP_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude"],
extensions: &["json"],
file_names: &["mcp.json", ".mcp.json"],
};
pub static DEPENDENCY_PATTERNS: FilePattern = FilePattern {
root_dirs: &[],
extensions: &["json", "toml", "txt", "lock"],
file_names: &[
"package.json",
"package-lock.json",
"Cargo.toml",
"Cargo.lock",
"requirements.txt",
"Pipfile",
"Pipfile.lock",
"pyproject.toml",
"poetry.lock",
"go.mod",
"go.sum",
"Gemfile",
"Gemfile.lock",
"composer.json",
"composer.lock",
"pom.xml",
"build.gradle",
"build.gradle.kts",
],
};
pub static DOCKER_PATTERNS: FilePattern = FilePattern {
root_dirs: &[],
extensions: &["yml", "yaml"],
file_names: &[
"Dockerfile",
"dockerfile",
"docker-compose.yml",
"docker-compose.yaml",
"compose.yml",
"compose.yaml",
],
};
pub static HOOK_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude"],
extensions: &["json"],
file_names: &["settings.json"],
};
pub static SUBAGENT_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude/agents"],
extensions: &["md", "yaml", "yml"],
file_names: &[],
};
pub static RULES_DIR_PATTERNS: FilePattern = FilePattern {
root_dirs: &[".claude/rules", "rules"],
extensions: &["md", "yaml", "yml"],
file_names: &[],
};
pub static PLUGIN_PATTERNS: FilePattern = FilePattern {
root_dirs: &[],
extensions: &["json"],
file_names: &["plugin.json", "marketplace.json"],
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_patterns_matches_skill_md() {
assert!(SKILL_PATTERNS.matches(Path::new("SKILL.md")));
assert!(SKILL_PATTERNS.matches(Path::new("path/to/SKILL.md")));
}
#[test]
fn test_skill_patterns_matches_claude_md() {
assert!(SKILL_PATTERNS.matches(Path::new("CLAUDE.md")));
assert!(SKILL_PATTERNS.matches(Path::new(".claude/CLAUDE.md")));
}
#[test]
fn test_skill_patterns_matches_md_extension() {
assert!(SKILL_PATTERNS.matches(Path::new("readme.md")));
assert!(!SKILL_PATTERNS.matches(Path::new("file.txt")));
}
#[test]
fn test_command_patterns() {
assert!(COMMAND_PATTERNS.matches(Path::new("test.md")));
assert!(!COMMAND_PATTERNS.matches(Path::new("test.txt")));
}
#[test]
fn test_mcp_patterns() {
assert!(MCP_PATTERNS.matches(Path::new("mcp.json")));
assert!(MCP_PATTERNS.matches(Path::new(".mcp.json")));
assert!(!MCP_PATTERNS.matches(Path::new("config.yaml")));
}
#[test]
fn test_dependency_patterns() {
assert!(DEPENDENCY_PATTERNS.matches(Path::new("package.json")));
assert!(DEPENDENCY_PATTERNS.matches(Path::new("Cargo.toml")));
assert!(DEPENDENCY_PATTERNS.matches(Path::new("requirements.txt")));
assert!(!DEPENDENCY_PATTERNS.matches(Path::new("README.md")));
}
#[test]
fn test_docker_patterns() {
assert!(DOCKER_PATTERNS.matches(Path::new("Dockerfile")));
assert!(DOCKER_PATTERNS.matches(Path::new("docker-compose.yml")));
assert!(DOCKER_PATTERNS.matches(Path::new("compose.yaml")));
}
#[test]
fn test_is_in_root_dir() {
let base = Path::new("/project");
assert!(SKILL_PATTERNS.is_in_root_dir(Path::new("/project/.claude/CLAUDE.md"), base));
assert!(SKILL_PATTERNS.is_in_root_dir(Path::new("/project/scripts/helper.md"), base));
assert!(!SKILL_PATTERNS.is_in_root_dir(Path::new("/project/other/file.md"), base));
}
}