fix(skills): preserve trust for skills-sh identifiers + reduce resolution churn (#3251)
* fix(skills): reduce skills.sh resolution churn and preserve trust for wrapped identifiers - Accept common skills.sh prefix typos (skils-sh/, skils.sh/) - Strip skills-sh/ prefix in _resolve_trust_level() so trusted repos stay trusted when installed through skills.sh - Use resolved identifier (from bundle/meta) for scan_skill source - Prefer tree search before root scan in _discover_identifier() - Add _resolve_github_meta() consolidation for inspect flow Cherry-picked from PR #3001 by kshitijk4poor. * fix: restore candidate loop in SkillsShSource.fetch() for consistency The cherry-picked PR only tried the first candidate identifier in fetch() while inspect() (via _resolve_github_meta) tried all four. This meant skills at repo/skills/path would be found by inspect but missed by fetch, forcing it through the heavier _discover_identifier flow. Restore the candidate loop so both paths behave identically. Updated the test assertion to match. --------- Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
This commit is contained in:
@@ -1050,15 +1050,27 @@ def _get_configured_model() -> str:
|
||||
|
||||
def _resolve_trust_level(source: str) -> str:
|
||||
"""Map a source identifier to a trust level."""
|
||||
prefix_aliases = (
|
||||
"skills-sh/",
|
||||
"skills.sh/",
|
||||
"skils-sh/",
|
||||
"skils.sh/",
|
||||
)
|
||||
normalized_source = source
|
||||
for prefix in prefix_aliases:
|
||||
if normalized_source.startswith(prefix):
|
||||
normalized_source = normalized_source[len(prefix):]
|
||||
break
|
||||
|
||||
# Agent-created skills get their own permissive trust level
|
||||
if source == "agent-created":
|
||||
if normalized_source == "agent-created":
|
||||
return "agent-created"
|
||||
# Official optional skills shipped with the repo
|
||||
if source.startswith("official/") or source == "official":
|
||||
if normalized_source.startswith("official/") or normalized_source == "official":
|
||||
return "builtin"
|
||||
# Check if source matches any trusted repo
|
||||
for trusted in TRUSTED_REPOS:
|
||||
if source.startswith(trusted) or source == trusted:
|
||||
if normalized_source.startswith(trusted) or normalized_source == trusted:
|
||||
return "trusted"
|
||||
return "community"
|
||||
|
||||
|
||||
@@ -925,19 +925,10 @@ class SkillsShSource(SkillSource):
|
||||
|
||||
def inspect(self, identifier: str) -> Optional[SkillMeta]:
|
||||
canonical = self._normalize_identifier(identifier)
|
||||
detail: Optional[dict] = None
|
||||
for candidate in self._candidate_identifiers(canonical):
|
||||
meta = self.github.inspect(candidate)
|
||||
if meta:
|
||||
detail = self._fetch_detail_page(canonical)
|
||||
return self._finalize_inspect_meta(meta, canonical, detail)
|
||||
|
||||
detail = self._fetch_detail_page(canonical)
|
||||
resolved = self._discover_identifier(canonical, detail=detail)
|
||||
if resolved:
|
||||
meta = self.github.inspect(resolved)
|
||||
if meta:
|
||||
return self._finalize_inspect_meta(meta, canonical, detail)
|
||||
meta = self._resolve_github_meta(canonical, detail=detail)
|
||||
if meta:
|
||||
return self._finalize_inspect_meta(meta, canonical, detail)
|
||||
return None
|
||||
|
||||
def _featured_skills(self, limit: int) -> List[SkillMeta]:
|
||||
@@ -1099,6 +1090,13 @@ class SkillsShSource(SkillSource):
|
||||
if self._matches_skill_tokens(meta, tokens):
|
||||
return meta.identifier
|
||||
|
||||
# Prefer a single recursive tree lookup before brute-forcing every
|
||||
# top-level directory. This avoids large request bursts on categorized
|
||||
# repos like borghei/claude-skills.
|
||||
tree_result = self.github._find_skill_in_repo_tree(repo, skill_token)
|
||||
if tree_result:
|
||||
return tree_result
|
||||
|
||||
# Fallback: scan repo root for directories that might contain skills
|
||||
try:
|
||||
root_url = f"https://api.github.com/repos/{repo}/contents/"
|
||||
@@ -1131,14 +1129,17 @@ class SkillsShSource(SkillSource):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Final fallback: use the GitHub Trees API to find the skill anywhere
|
||||
# in the repo tree. This handles deeply nested structures like
|
||||
# cli-tool/components/skills/development/<skill>/ that the shallow
|
||||
# scan above can't reach.
|
||||
tree_result = self.github._find_skill_in_repo_tree(repo, skill_token)
|
||||
if tree_result:
|
||||
return tree_result
|
||||
return None
|
||||
|
||||
def _resolve_github_meta(self, identifier: str, detail: Optional[dict] = None) -> Optional[SkillMeta]:
|
||||
for candidate in self._candidate_identifiers(identifier):
|
||||
meta = self.github.inspect(candidate)
|
||||
if meta:
|
||||
return meta
|
||||
|
||||
resolved = self._discover_identifier(identifier, detail=detail)
|
||||
if resolved:
|
||||
return self.github.inspect(resolved)
|
||||
return None
|
||||
|
||||
def _finalize_inspect_meta(self, meta: SkillMeta, canonical: str, detail: Optional[dict]) -> SkillMeta:
|
||||
@@ -1264,10 +1265,15 @@ class SkillsShSource(SkillSource):
|
||||
|
||||
@staticmethod
|
||||
def _normalize_identifier(identifier: str) -> str:
|
||||
if identifier.startswith("skills-sh/"):
|
||||
return identifier[len("skills-sh/"):]
|
||||
if identifier.startswith("skills.sh/"):
|
||||
return identifier[len("skills.sh/"):]
|
||||
prefix_aliases = (
|
||||
"skills-sh/",
|
||||
"skills.sh/",
|
||||
"skils-sh/",
|
||||
"skils.sh/",
|
||||
)
|
||||
for prefix in prefix_aliases:
|
||||
if identifier.startswith(prefix):
|
||||
return identifier[len(prefix):]
|
||||
return identifier
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user