diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index d80e995..f3f8010 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -2977,244 +2977,6 @@ fn resolve_skill_path(skill: &str) -> Result { commands::resolve_skill_path(&cwd, skill).map_err(|error| error.to_string()) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum SkillLookupOrigin { - SkillsDir, - LegacyCommandsDir, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct SkillLookupRoot { - path: std::path::PathBuf, - origin: SkillLookupOrigin, -} - -fn skill_lookup_roots() -> Vec { - let mut roots = Vec::new(); - - if let Ok(cwd) = std::env::current_dir() { - push_project_skill_lookup_roots(&mut roots, &cwd); - } - - if let Ok(claw_config_home) = std::env::var("CLAW_CONFIG_HOME") { - push_prefixed_skill_lookup_roots(&mut roots, std::path::Path::new(&claw_config_home)); - } - if let Ok(codex_home) = std::env::var("CODEX_HOME") { - push_prefixed_skill_lookup_roots(&mut roots, std::path::Path::new(&codex_home)); - } - if let Ok(home) = std::env::var("HOME") { - push_home_skill_lookup_roots(&mut roots, std::path::Path::new(&home)); - } - if let Ok(claude_config_dir) = std::env::var("CLAUDE_CONFIG_DIR") { - let claude_config_dir = std::path::PathBuf::from(claude_config_dir); - push_skill_lookup_root( - &mut roots, - claude_config_dir.join("skills"), - SkillLookupOrigin::SkillsDir, - ); - push_skill_lookup_root( - &mut roots, - claude_config_dir.join("skills").join("omc-learned"), - SkillLookupOrigin::SkillsDir, - ); - push_skill_lookup_root( - &mut roots, - claude_config_dir.join("commands"), - SkillLookupOrigin::LegacyCommandsDir, - ); - } - push_skill_lookup_root( - &mut roots, - std::path::PathBuf::from("/home/bellman/.claw/skills"), - SkillLookupOrigin::SkillsDir, - ); - push_skill_lookup_root( - &mut roots, - std::path::PathBuf::from("/home/bellman/.codex/skills"), - SkillLookupOrigin::SkillsDir, - ); - - roots -} - -fn push_project_skill_lookup_roots(roots: &mut Vec, cwd: &std::path::Path) { - for ancestor in cwd.ancestors() { - push_prefixed_skill_lookup_roots(roots, &ancestor.join(".omc")); - push_prefixed_skill_lookup_roots(roots, &ancestor.join(".agents")); - push_prefixed_skill_lookup_roots(roots, &ancestor.join(".claw")); - push_prefixed_skill_lookup_roots(roots, &ancestor.join(".codex")); - push_prefixed_skill_lookup_roots(roots, &ancestor.join(".claude")); - } -} - -fn push_home_skill_lookup_roots(roots: &mut Vec, home: &std::path::Path) { - push_prefixed_skill_lookup_roots(roots, &home.join(".omc")); - push_prefixed_skill_lookup_roots(roots, &home.join(".claw")); - push_prefixed_skill_lookup_roots(roots, &home.join(".codex")); - push_prefixed_skill_lookup_roots(roots, &home.join(".claude")); - push_skill_lookup_root( - roots, - home.join(".agents").join("skills"), - SkillLookupOrigin::SkillsDir, - ); - push_skill_lookup_root( - roots, - home.join(".config").join("opencode").join("skills"), - SkillLookupOrigin::SkillsDir, - ); - push_skill_lookup_root( - roots, - home.join(".claude").join("skills").join("omc-learned"), - SkillLookupOrigin::SkillsDir, - ); -} - -fn push_prefixed_skill_lookup_roots(roots: &mut Vec, prefix: &std::path::Path) { - push_skill_lookup_root(roots, prefix.join("skills"), SkillLookupOrigin::SkillsDir); - push_skill_lookup_root( - roots, - prefix.join("commands"), - SkillLookupOrigin::LegacyCommandsDir, - ); -} - -fn push_skill_lookup_root( - roots: &mut Vec, - path: std::path::PathBuf, - origin: SkillLookupOrigin, -) { - if path.is_dir() && !roots.iter().any(|existing| existing.path == path) { - roots.push(SkillLookupRoot { path, origin }); - } -} - -fn resolve_skill_path_in_root( - root: &SkillLookupRoot, - requested: &str, -) -> Option { - match root.origin { - SkillLookupOrigin::SkillsDir => resolve_skill_path_in_skills_dir(&root.path, requested), - SkillLookupOrigin::LegacyCommandsDir => { - resolve_skill_path_in_legacy_commands_dir(&root.path, requested) - } - } -} - -fn resolve_skill_path_in_skills_dir( - root: &std::path::Path, - requested: &str, -) -> Option { - let direct = root.join(requested).join("SKILL.md"); - if direct.is_file() { - return Some(direct); - } - - let entries = std::fs::read_dir(root).ok()?; - for entry in entries.flatten() { - if !entry.path().is_dir() { - continue; - } - let skill_path = entry.path().join("SKILL.md"); - if !skill_path.is_file() { - continue; - } - if entry - .file_name() - .to_string_lossy() - .eq_ignore_ascii_case(requested) - || skill_frontmatter_name_matches(&skill_path, requested) - { - return Some(skill_path); - } - } - - None -} - -fn resolve_skill_path_in_legacy_commands_dir( - root: &std::path::Path, - requested: &str, -) -> Option { - let direct_dir = root.join(requested).join("SKILL.md"); - if direct_dir.is_file() { - return Some(direct_dir); - } - - let direct_markdown = root.join(format!("{requested}.md")); - if direct_markdown.is_file() { - return Some(direct_markdown); - } - - let entries = std::fs::read_dir(root).ok()?; - for entry in entries.flatten() { - let path = entry.path(); - let candidate_path = if path.is_dir() { - let skill_path = path.join("SKILL.md"); - if !skill_path.is_file() { - continue; - } - skill_path - } else if path - .extension() - .is_some_and(|ext| ext.to_string_lossy().eq_ignore_ascii_case("md")) - { - path - } else { - continue; - }; - - let matches_entry_name = candidate_path - .file_stem() - .is_some_and(|stem| stem.to_string_lossy().eq_ignore_ascii_case(requested)) - || entry - .file_name() - .to_string_lossy() - .trim_end_matches(".md") - .eq_ignore_ascii_case(requested); - if matches_entry_name || skill_frontmatter_name_matches(&candidate_path, requested) { - return Some(candidate_path); - } - } - - None -} - -fn skill_frontmatter_name_matches(path: &std::path::Path, requested: &str) -> bool { - std::fs::read_to_string(path) - .ok() - .and_then(|contents| parse_skill_name(&contents)) - .is_some_and(|name| name.eq_ignore_ascii_case(requested)) -} - -fn parse_skill_name(contents: &str) -> Option { - parse_skill_frontmatter_value(contents, "name") -} - -fn parse_skill_frontmatter_value(contents: &str, key: &str) -> Option { - let mut lines = contents.lines(); - if lines.next().map(str::trim) != Some("---") { - return None; - } - - for line in lines { - let trimmed = line.trim(); - if trimmed == "---" { - break; - } - if let Some(value) = trimmed.strip_prefix(&format!("{key}:")) { - let value = value - .trim() - .trim_matches(|ch| matches!(ch, '"' | '\'')) - .trim(); - if !value.is_empty() { - return Some(value.to_string()); - } - } - } - - None -} - const DEFAULT_AGENT_MODEL: &str = "claude-opus-4-6"; const DEFAULT_AGENT_SYSTEM_DATE: &str = "2026-03-31"; const DEFAULT_AGENT_MAX_ITERATIONS: usize = 32;