|
34 | 34 | }) |
35 | 35 |
|
36 | 36 |
|
| 37 | +def _resolve_skill_dir_path(skill_dir: Union[str, pathlib.Path]) -> pathlib.Path: |
| 38 | + """Resolve a skill path consistently across execution environments. |
| 39 | +
|
| 40 | + Relative skill paths may be authored from a parent folder (for example, |
| 41 | + ``agent_name/skills/my-skill``) while execution happens with cwd already set |
| 42 | + to ``agent_name``. In that case, naively resolving against cwd produces a |
| 43 | + duplicated segment (``agent_name/agent_name/...``). |
| 44 | +
|
| 45 | + Args: |
| 46 | + skill_dir: Raw skill directory path provided by caller. |
| 47 | +
|
| 48 | + Returns: |
| 49 | + A best-effort resolved path. |
| 50 | + """ |
| 51 | + path = pathlib.Path(skill_dir) |
| 52 | + if path.is_absolute(): |
| 53 | + return path.resolve() |
| 54 | + |
| 55 | + cwd = pathlib.Path.cwd() |
| 56 | + candidates = [cwd / path] |
| 57 | + |
| 58 | + if path.parts and path.parts[0] == cwd.name: |
| 59 | + stripped = pathlib.Path(*path.parts[1:]) |
| 60 | + candidates.append(cwd / stripped) |
| 61 | + |
| 62 | + candidates.append(cwd.parent / path) |
| 63 | + |
| 64 | + for candidate in candidates: |
| 65 | + if candidate.exists(): |
| 66 | + return candidate.resolve() |
| 67 | + |
| 68 | + return candidates[0].resolve() |
| 69 | + |
| 70 | + |
37 | 71 | def _load_dir(directory: pathlib.Path) -> dict[str, str]: |
38 | 72 | """Recursively load files from a directory into a dictionary. |
39 | 73 |
|
@@ -122,7 +156,7 @@ def _load_skill_from_dir(skill_dir: Union[str, pathlib.Path]) -> models.Skill: |
122 | 156 | ValueError: If SKILL.md is invalid or the skill name does not match |
123 | 157 | the directory name. |
124 | 158 | """ |
125 | | - skill_dir = pathlib.Path(skill_dir).resolve() |
| 159 | + skill_dir = _resolve_skill_dir_path(skill_dir) |
126 | 160 |
|
127 | 161 | parsed, body, skill_md = _parse_skill_md(skill_dir) |
128 | 162 |
|
@@ -171,7 +205,7 @@ def _validate_skill_dir( |
171 | 205 | List of problem strings. Empty list means the skill is valid. |
172 | 206 | """ |
173 | 207 | problems: list[str] = [] |
174 | | - skill_dir = pathlib.Path(skill_dir).resolve() |
| 208 | + skill_dir = _resolve_skill_dir_path(skill_dir) |
175 | 209 |
|
176 | 210 | if not skill_dir.exists(): |
177 | 211 | return [f"Directory '{skill_dir}' does not exist."] |
@@ -229,6 +263,6 @@ def _read_skill_properties( |
229 | 263 | FileNotFoundError: If the directory or SKILL.md is not found. |
230 | 264 | ValueError: If the frontmatter is invalid. |
231 | 265 | """ |
232 | | - skill_dir = pathlib.Path(skill_dir).resolve() |
| 266 | + skill_dir = _resolve_skill_dir_path(skill_dir) |
233 | 267 | parsed, _, _ = _parse_skill_md(skill_dir) |
234 | 268 | return models.Frontmatter.model_validate(parsed) |
0 commit comments