diff --git a/agent/skill_commands.py b/agent/skill_commands.py index 909305e788..5944116179 100644 --- a/agent/skill_commands.py +++ b/agent/skill_commands.py @@ -458,28 +458,20 @@ def scan_skill_commands() -> Dict[str, Dict[str, Any]]: # (#74574). commands: Dict[str, Dict[str, Any]] = {} try: - from tools.skills_tool import _skills_dir, _get_disabled_skill_names - from agent.skill_utils import ( - get_external_skills_dirs, get_project_skills_dirs, iter_project_skill_files, iter_skill_index_files, - ) + from tools.skills_tool import _get_disabled_skill_names, _skill_catalog disabled = _get_disabled_skill_names() seen_names: set = set() - # Precedence: project (through the quarantine chokepoint) > local > external. - # Resolve the local dir at call time: import-time SKILLS_DIR is frozen to - # the launch home, but a multiplexed profile scope may have changed it. - # See #67277. - skills_dir = _skills_dir() - iters = [iter_project_skill_files(d) for d in get_project_skills_dirs()] - local = [skills_dir] if skills_dir.exists() else [] - iters += [iter_skill_index_files(d, "SKILL.md") for d in local + get_external_skills_dirs()] - for _iter in iters: - for skill_md in _iter: + # Only names skill_view resolves to exactly this file get a /command: the shared catalog applies + # project > local > create_dir > external precedence (live profile dir, #67277) and leaves + # same-tier duplicates to their exact paths (`/skill a/one`), so a bare slug never guesses. + for entry in _skill_catalog(): + if entry["status"] == "unique": try: - _scan_skill_md(skill_md, disabled, seen_names, commands) + _scan_skill_md(Path(entry["path"]), disabled, seen_names, commands) except Exception: continue except Exception: - pass + logger.debug("Skill command scan failed", exc_info=True) # Publish the scanned map atomically: a reader must see a consistent # (key, map) pair. Only the publish/lookup pair is locked; the scan above # (file I/O, deferred imports) stays outside it (#14536, #74574). @@ -536,6 +528,8 @@ def reload_skills() -> Dict[str, Any]: # Clear the entire multi-slot cache: a skill edit could affect any # platform/profile combination, so every cached identity must rescan. _skill_commands_by_key.clear() + from tools import skills_tool + skills_tool._SKILLS_CACHE.clear() # the scan reads the shared catalog; an explicit reload must not hit its TTL before = command_snapshot(before_commands) new_commands = scan_skill_commands() result = diff_command_snapshots(before, command_snapshot(new_commands))