# Load project config (projects//conf.json) for tickets spooler and authorized_emails. # Requires PROJECT_ROOT (repo root with ai_project_id) and GITEA_ISSUES_DIR (ia_dev/gitea-issues). from __future__ import annotations import json import os from pathlib import Path def project_root() -> Path: """Project repo root (parent of ia_dev). Where data/issues/ and ai_project_id live.""" env_root = os.environ.get("PROJECT_ROOT") if env_root: return Path(env_root).resolve() env_repo = os.environ.get("REPO_ROOT") if env_repo: root = Path(env_repo).resolve() # If REPO_ROOT is ia_dev, project root is parent if (root / "gitea-issues").is_dir(): return root.parent return root issues_dir = os.environ.get("GITEA_ISSUES_DIR") if issues_dir: return Path(issues_dir).resolve().parent.parent return Path(__file__).resolve().parent.parent.parent def ia_dev_root() -> Path: """Directory containing gitea-issues (ia_dev).""" issues_dir = os.environ.get("GITEA_ISSUES_DIR") if issues_dir: return Path(issues_dir).resolve().parent return Path(__file__).resolve().parent.parent def load_project_config() -> dict | None: """Load projects//conf.json. Returns None if not found or slug missing.""" root = project_root() ia_dev = ia_dev_root() slug_path = root / "ai_project_id" if not slug_path.is_file(): slug_path = root / ".ia_project" slug = os.environ.get("IA_PROJECT", "").strip() if os.environ.get("IA_PROJECT") else None if not slug and slug_path.is_file(): slug = slug_path.read_text(encoding="utf-8").strip() if not slug: return None conf_path = ia_dev / "projects" / slug / "conf.json" if not conf_path.is_file(): return None with open(conf_path, encoding="utf-8") as f: return json.load(f) def project_dir() -> Path | None: """Path to projects// (under ia_dev). None if project config not found.""" root = project_root() ia_dev = ia_dev_root() slug = os.environ.get("IA_PROJECT", "").strip() if not slug and (root / "ai_project_id").is_file(): slug = (root / "ai_project_id").read_text(encoding="utf-8").strip() if not slug and (root / ".ia_project").is_file(): slug = (root / ".ia_project").read_text(encoding="utf-8").strip() if not slug: return None return ia_dev / "projects" / slug def data_issues_dir() -> Path: """Path to data/issues/ spooler under projects// (ia_dev/projects//data/issues).""" pd = project_dir() if pd is not None: return pd / "data" / "issues" return project_root() / "data" / "issues" def project_logs_dir() -> Path: """Path to logs/ under projects// (ia_dev/projects//logs).""" pd = project_dir() if pd is not None: return pd / "logs" return project_root() / "logs" def authorized_emails() -> dict[str, str | list[str]]: """Return tickets.authorized_emails (to, from list). Empty dict if missing.""" conf = load_project_config() if not conf: return {} tickets = conf.get("tickets") or {} return tickets.get("authorized_emails") or {}