"""Tool filesystem scope — environment (safe root) vs project workspace jail.""" from __future__ import annotations import os from pathlib import Path from butler.configuration.settings import get_butler_settings from butler.defaults.env_defaults import ( TOOL_PROJECT_ANCHOR_DEFAULT, TOOL_SCOPE_DEFAULT, WORKSPACE_ANCHOR_STRICT_DEFAULT, ) from butler.project.policy_env import bind_default_project_enabled def environment_tool_scope_enabled() -> bool: """When true, tools use BUTLER_TOOL_SAFE_ROOT (or repo root), not project workspace jail.""" raw = (os.getenv("BUTLER_TOOL_SCOPE", TOOL_SCOPE_DEFAULT) or TOOL_SCOPE_DEFAULT).strip().lower() if raw in ("project", "workspace", "0", "false", "off"): return False return True def resolve_environment_tool_root() -> Path: from butler.tools.path_safety_ops import configured_safe_root_safe configured = configured_safe_root_safe() if configured is not None: return configured settings = get_butler_settings() return settings.projects_dir.expanduser().resolve().parent def workspace_anchor_strict_for_paths() -> bool: """Relative paths anchor to project workspace only in legacy project scope + strict flag.""" if environment_tool_scope_enabled(): return False return os.getenv("BUTLER_WORKSPACE_ANCHOR_STRICT", WORKSPACE_ANCHOR_STRICT_DEFAULT).strip().lower() in ( "1", "true", "yes", "on", ) def project_relative_path_anchors_to_workspace() -> bool: """When a project is active, relative paths resolve under its workspace (even in environment scope).""" if not environment_tool_scope_enabled(): return workspace_anchor_strict_for_paths() raw = (os.getenv("BUTLER_TOOL_PROJECT_ANCHOR", TOOL_PROJECT_ANCHOR_DEFAULT) or TOOL_PROJECT_ANCHOR_DEFAULT).strip().lower() return raw in ("1", "true", "yes", "on") __all__ = [ "bind_default_project_enabled", "environment_tool_scope_enabled", "project_relative_path_anchors_to_workspace", "resolve_environment_tool_root", "workspace_anchor_strict_for_paths", ]