diff --git a/src/skillport/modules/skills/internal/__init__.py b/src/skillport/modules/skills/internal/__init__.py index a3ba75a..e32acfc 100644 --- a/src/skillport/modules/skills/internal/__init__.py +++ b/src/skillport/modules/skills/internal/__init__.py @@ -8,6 +8,7 @@ get_latest_commit_sha, get_remote_tree_hash, parse_github_url, + rename_single_skill_dir, ) from .manager import ( SkillInfo, @@ -45,6 +46,7 @@ "fetch_github_source_with_info", "get_latest_commit_sha", "get_remote_tree_hash", + "rename_single_skill_dir", "ParsedGitHubURL", "GitHubFetchResult", "record_origin", diff --git a/src/skillport/modules/skills/internal/github.py b/src/skillport/modules/skills/internal/github.py index 09b5862..536a297 100644 --- a/src/skillport/modules/skills/internal/github.py +++ b/src/skillport/modules/skills/internal/github.py @@ -1,5 +1,6 @@ import os import re +import shutil import tarfile import tempfile from collections.abc import Iterable @@ -287,6 +288,30 @@ def _fetch_tree(parsed: ParsedGitHubURL, token: str | None) -> dict: return data +def rename_single_skill_dir(extracted_dir: Path, skill_name: str) -> Path: + """Rename extracted GitHub directory to match single skill name. + + When a GitHub repo contains a single skill, the extracted temp directory + has a random name (skillport-gh-*). This renames it to match the skill name + from SKILL.md frontmatter for consistency. + + Args: + extracted_dir: The temporary extraction directory + skill_name: The skill name from SKILL.md + + Returns: + The renamed path (or original if no rename needed) + """ + if skill_name == extracted_dir.name: + return extracted_dir + + renamed = extracted_dir.parent / skill_name + if renamed.exists(): + shutil.rmtree(renamed) + extracted_dir.rename(renamed) + return renamed + + def get_remote_tree_hash(parsed: ParsedGitHubURL, token: str | None, path: str) -> str: """Compute remote content hash for a skill path using the GitHub tree API. diff --git a/src/skillport/modules/skills/public/add.py b/src/skillport/modules/skills/public/add.py index dda46f2..a52b9d6 100644 --- a/src/skillport/modules/skills/public/add.py +++ b/src/skillport/modules/skills/public/add.py @@ -15,6 +15,7 @@ fetch_github_source_with_info, parse_github_url, record_origin, + rename_single_skill_dir, resolve_source, ) from skillport.shared.config import Config @@ -96,14 +97,9 @@ def add_skill( # before adding it to the local catalog. if source_type == SourceType.GITHUB and len(skills) == 1: single = skills[0] - if single.name != source_path.name: - renamed = source_path.parent / single.name - if renamed.exists(): - shutil.rmtree(renamed) - source_path.rename(renamed) - source_path = renamed - temp_dir = renamed - skills = detect_skills(source_path) + source_path = rename_single_skill_dir(source_path, single.name) + temp_dir = source_path + skills = detect_skills(source_path) # 単一スキルの場合は origin.path をスキル名で確定させる if origin_payload is not None: origin_payload["path"] = origin_payload.get("path") or single.name diff --git a/src/skillport/modules/skills/public/update.py b/src/skillport/modules/skills/public/update.py index 101231c..afdd400 100644 --- a/src/skillport/modules/skills/public/update.py +++ b/src/skillport/modules/skills/public/update.py @@ -17,6 +17,7 @@ get_origin, get_remote_tree_hash, parse_github_url, + rename_single_skill_dir, update_origin, ) from skillport.shared.config import Config @@ -552,13 +553,9 @@ def _sync_stored_hash_if_needed() -> None: skills = detect_skills(temp_dir) if skills: source_skill_path = temp_dir - if len(skills) == 1 and skills[0].name != temp_dir.name: - renamed = temp_dir.parent / skills[0].name - if renamed.exists(): - shutil.rmtree(renamed) - temp_dir.rename(renamed) - source_skill_path = renamed - temp_dir = renamed + if len(skills) == 1: + source_skill_path = rename_single_skill_dir(temp_dir, skills[0].name) + temp_dir = source_skill_path shutil.copytree(source_skill_path, dest_path) else: shutil.copytree(temp_dir, dest_path)