Load skills from a directory Recursively scans the directory for skill files and attempts to parse them. Supported layouts: - `path/to/skill.md` - `path/to/skill/SKILL.md` Candidate files are processed in deterministic sorted order. Files that fail to parse are skipped with debug logging; validation failures are logged as warnings.
(&self, dir: impl AsRef<Path>)
| 122 | /// fail to parse are skipped with debug logging; validation failures are |
| 123 | /// logged as warnings. |
| 124 | pub fn load_from_dir(&self, dir: impl AsRef<Path>) -> anyhow::Result<usize> { |
| 125 | let dir = dir.as_ref(); |
| 126 | |
| 127 | if !dir.exists() { |
| 128 | return Ok(0); |
| 129 | } |
| 130 | |
| 131 | if !dir.is_dir() { |
| 132 | anyhow::bail!("Path is not a directory: {}", dir.display()); |
| 133 | } |
| 134 | |
| 135 | let mut loaded = 0; |
| 136 | for candidate in Self::collect_skill_candidates(dir)? { |
| 137 | match Skill::from_file(&candidate) { |
| 138 | Ok(skill) => { |
| 139 | let name = skill.name.clone(); |
| 140 | if skill.allowed_tools.is_none() { |
| 141 | tracing::warn!( |
| 142 | skill = %name, |
| 143 | path = %candidate.display(), |
| 144 | "Skill omits allowed-tools; Skill invocation is fail-secure and will deny tool use until allowed-tools is declared" |
| 145 | ); |
| 146 | } else if skill.uses_legacy_allowed_tools_syntax() { |
| 147 | tracing::warn!( |
| 148 | skill = %name, |
| 149 | path = %candidate.display(), |
| 150 | "Skill uses legacy whitespace-separated allowed-tools; use comma-separated permissions such as Read(*), Write(*), Bash(*) or a YAML list" |
| 151 | ); |
| 152 | } |
| 153 | let skill = Arc::new(skill); |
| 154 | if self.get(&name).is_some() { |
| 155 | tracing::warn!( |
| 156 | skill = %name, |
| 157 | path = %candidate.display(), |
| 158 | "Duplicate skill name encountered during directory load — overriding previous definition" |
| 159 | ); |
| 160 | } |
| 161 | match self.register(skill) { |
| 162 | Ok(()) => loaded += 1, |
| 163 | Err(e) => { |
| 164 | tracing::warn!( |
| 165 | "Skill validation failed for {}: {}", |
| 166 | candidate.display(), |
| 167 | e |
| 168 | ); |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | Err(e) => { |
| 173 | tracing::debug!("Skipped {}: {}", candidate.display(), e); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | Ok(loaded) |
| 179 | } |
| 180 | |
| 181 | fn collect_skill_candidates(dir: &Path) -> anyhow::Result<Vec<PathBuf>> { |