"""Configuration loaded from environment / .env. Secrets never reach the browser. The frontend talks only to this local backend; the backend holds the GitHub PAT and Fireworks key. """ import os from functools import lru_cache from pathlib import Path from dotenv import load_dotenv load_dotenv() _REPO_ROOT = Path(__file__).resolve().parent.parent _CACHE_HOME = Path.home() / ".cache" / "pr-reviewer" class Config: # --- GitHub --- # Either provide GITHUB_TOKEN (a PAT) here, or leave it blank to fall # back to `gh auth token` from your local gh CLI (see github_client.py). GITHUB_TOKEN: str = os.getenv("GITHUB_TOKEN", "").strip() GITHUB_ORG: str = os.getenv("GITHUB_ORG", "Sea-Haven-Industries").strip() # The search filter that defines the review queue. PR_SEARCH_FILTER: str = os.getenv( "PR_SEARCH_FILTER", "is:pr state:open archived:false sort:updated-desc org:Sea-Haven-Industries", ).strip() # Cap how many PRs we pull per refresh. GitHub search returns at most 100 # per page; PRs beyond that are not paginated (fine at current org volume). MAX_PRS: int = int(os.getenv("MAX_PRS", "100")) # Skip diffs larger than this many bytes (keeps token cost sane). MAX_DIFF_BYTES: int = int(os.getenv("MAX_DIFF_BYTES", "120000")) # --- Fireworks (OpenAI-compatible endpoint) --- FIREWORKS_API_KEY: str = os.getenv("FIREWORKS_API_KEY", "").strip() FIREWORKS_BASE_URL: str = os.getenv( "FIREWORKS_BASE_URL", "https://api.fireworks.ai/inference/v1" ).strip() # Swap this to any Fireworks model id you like. Coding-strong defaults: # accounts/fireworks/models/deepseek-v4-pro # accounts/fireworks/models/kimi-k2p6 FIREWORKS_MODEL: str = os.getenv( "FIREWORKS_MODEL", "accounts/fireworks/models/deepseek-v4-pro" ).strip() FIREWORKS_TEMPERATURE: float = float(os.getenv("FIREWORKS_TEMPERATURE", "0.2")) FIREWORKS_MAX_TOKENS: int = int(os.getenv("FIREWORKS_MAX_TOKENS", "4000")) # --- Review behavior --- # If a PR author's login is in this list (case-insensitive), the review # body will @-mention them. Handles the @openswe bot case. MENTION_AUTHORS: list[str] = [ a.strip().lower() for a in os.getenv("MENTION_AUTHORS", "openswe").split(",") if a.strip() ] HOST: str = os.getenv("HOST", "127.0.0.1") PORT: int = int(os.getenv("PORT", "8765")) # --- Background auto-review worker --- # Seconds between poll cycles. The worker fetches the queue, then pre-reviews # any new or changed non-draft PR so results are ready before you open them. POLL_INTERVAL: int = int(os.getenv("POLL_INTERVAL", "300")) # How many PRs to review in parallel per cycle. Kept low for a single-user # tool to stay well under Fireworks rate limits. WORKER_CONCURRENCY: int = int(os.getenv("WORKER_CONCURRENCY", "2")) # Give up auto-retrying a PR that keeps failing until its diff changes. MAX_REVIEW_ATTEMPTS: int = int(os.getenv("MAX_REVIEW_ATTEMPTS", "3")) # SQLite cache of pre-computed reviews. Absolute path resolved from the repo # root so it lands in the same place regardless of the working directory. CACHE_DB: str = os.getenv("CACHE_DB", str(_REPO_ROOT / "pr_cache.db")) # --- Engineering-handbook grounding --- # When enabled, the reviewer is fed a distilled digest of the Sea Haven # engineering-handbook conventions, refreshed daily from an app-managed clone. HANDBOOK_ENABLED: bool = os.getenv("HANDBOOK_ENABLED", "true").lower() in ( "1", "true", "yes", "on", ) HANDBOOK_REPO_URL: str = os.getenv( "HANDBOOK_REPO_URL", "https://github.com/Sea-Haven-Industries/engineering-handbook.git", ) # App-managed clone + digest cache, kept outside the repo (in ~/.cache). HANDBOOK_CACHE_DIR: str = os.getenv( "HANDBOOK_CACHE_DIR", str(_CACHE_HOME / "handbook") ) HANDBOOK_DIGEST_PATH: str = os.getenv( "HANDBOOK_DIGEST_PATH", str(_CACHE_HOME / "handbook_digest.json") ) # Re-pull and re-distill the handbook at most this often. HANDBOOK_REFRESH_HOURS: int = int(os.getenv("HANDBOOK_REFRESH_HOURS", "24")) @lru_cache def get_config() -> Config: return Config()