from __future__ import annotations import glob as glob_mod import logging import os from collections.abc import Iterator from pathlib import Path from pydantic import BaseModel, ConfigDict _log = logging.getLogger(__name__) class SourceRoot(BaseModel): model_config = ConfigDict(frozen=True) label: str glob: str def walk_sources(sources: list[SourceRoot]) -> Iterator[tuple[Path, SourceRoot]]: """Yield (path, source_root) for every readable SKILL.md matched by a glob.""" for root in sources: pattern = os.path.expanduser(root.glob) for match in glob_mod.glob(pattern, recursive=True): path = Path(match) if not path.is_file(): _log.warning("discovery: skipping non-file %s", path) continue try: path.stat() except OSError as e: _log.warning("discovery: unreadable %s: %s", path, e) continue yield path, root