""" Paths - Centralized path resolution for configuration and environment variables. This module provides centralized path resolution for all configurable paths in the application. All paths can be overridden via environment variables or config.toml. Environment Variables: SLOP_CONFIG: Path to config.toml SLOP_CONDUCTOR_DIR: Path to conductor directory SLOP_LOGS_DIR: Path to logs directory SLOP_SCRIPTS_DIR: Path to generated scripts directory Configuration (config.toml): [paths] conductor_dir = "conductor" logs_dir = "logs/sessions" scripts_dir = "scripts/generated" Path Functions: get_config_path() -> Path to config.toml get_conductor_dir() -> Path to conductor directory get_logs_dir() -> Path to logs/sessions get_scripts_dir() -> Path to scripts/generated get_tracks_dir() -> Path to conductor/tracks get_track_state_dir(track_id) -> Path to conductor/tracks/ get_archive_dir() -> Path to conductor/archive Resolution Order: 1. Check environment variable 2. Check config.toml [paths] section 3. Fall back to default Usage: from src.paths import get_logs_dir, get_scripts_dir logs_dir = get_logs_dir() scripts_dir = get_scripts_dir() See Also: - docs/guide_tools.md for configuration documentation - src/session_logger.py for logging paths - src/project_manager.py for project paths """ from pathlib import Path import os import tomllib from typing import Optional _RESOLVED: dict[str, Path] = {} def get_config_path() -> Path: root_dir = Path(__file__).resolve().parent.parent return Path(os.environ.get("SLOP_CONFIG", root_dir / "config.toml")) def _resolve_path(env_var: str, config_key: str, default: str) -> Path: if env_var in os.environ: return Path(os.environ[env_var]) try: with open(get_config_path(), "rb") as f: cfg = tomllib.load(f) if "paths" in cfg and config_key in cfg["paths"]: return Path(cfg["paths"][config_key]) except FileNotFoundError: pass return Path(default) def get_conductor_dir() -> Path: if "conductor_dir" not in _RESOLVED: _RESOLVED["conductor_dir"] = _resolve_path("SLOP_CONDUCTOR_DIR", "conductor_dir", "conductor") return _RESOLVED["conductor_dir"] def get_logs_dir() -> Path: if "logs_dir" not in _RESOLVED: _RESOLVED["logs_dir"] = _resolve_path("SLOP_LOGS_DIR", "logs_dir", "logs/sessions") return _RESOLVED["logs_dir"] def get_scripts_dir() -> Path: if "scripts_dir" not in _RESOLVED: _RESOLVED["scripts_dir"] = _resolve_path("SLOP_SCRIPTS_DIR", "scripts_dir", "scripts/generated") return _RESOLVED["scripts_dir"] def get_tracks_dir() -> Path: return get_conductor_dir() / "tracks" def get_track_state_dir(track_id: str) -> Path: return get_tracks_dir() / track_id def get_archive_dir() -> Path: return get_conductor_dir() / "archive" def reset_resolved() -> None: """For testing only - clear cached resolutions.""" _RESOLVED.clear()