"""Fixture-file expansion: resolve eval-case ``files`` globs into injectable text. The same ``base`` directory bounds both the glob expansion here and the agent's read tools (see :mod:`skill_eval_runner.agent`); any path that resolves outside it is silently skipped so a suite can never read beyond its sandbox. """ import logging from pathlib import Path logger = logging.getLogger(__name__) _GLOB_CHARS = ("*", "?", "[") def expand_files(patterns: list[str], base: Path) -> list[tuple[str, str]]: """Expand path patterns relative to ``base`` into ``(relative_path, content)`` pairs. Patterns may contain glob wildcards; those without are treated as literal paths. Every match must be a regular file inside ``base`` — others are skipped. Results are de-duplicated and ordered by first appearance. """ base_resolved = base.resolve() results: list[tuple[str, str]] = [] seen: set[Path] = set() for pattern in patterns: matches = ( sorted(base.glob(pattern)) if any(c in pattern for c in _GLOB_CHARS) else [base / pattern] ) for match in matches: resolved = match.resolve() if not resolved.is_relative_to(base_resolved): continue if not resolved.is_file() or resolved in seen: continue seen.add(resolved) rel = str(resolved.relative_to(base_resolved)) logger.debug("expand_files matched %s", rel) results.append((rel, resolved.read_text())) return results