"""Konfiguration: Provider-Stacks, Rollen-Auflösung, Engine-Konstanten. Rollen-Modelle stehen AUSSCHLIESSLICH in der .env (ROLLE_=provider:modell) — nirgendwo im Code ein Default oder Fallback. Unkonfigurierte Rolle = harter Stopp. """ import os from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent TEMPLATES_DIR = Path(__file__).resolve().parent / "templates" STORAGE_DIR = PROJECT_ROOT / "storage" DB_PATH = STORAGE_DIR / "planer.db" LAEUFE_DIR = STORAGE_DIR / "laeufe" OPENCODE_CONFIG = PROJECT_ROOT / "dev-ops" / "opencode-slim.json" PROJEKTE_DATEI = PROJECT_ROOT / "projekte.txt" def _load_env(path: Path) -> None: """Mini .env-Loader (keine Dependency): KEY=VALUE-Zeilen. Die DATEI gewinnt über vererbte Env (Creator-Lehre: vererbte Werte pinnen sonst still veraltete Stände).""" try: text = path.read_text(encoding="utf-8") except OSError: return for line in text.splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") key, value = key.strip(), value.strip().strip('"').strip("'") if key: os.environ[key] = value _load_env(PROJECT_ROOT / ".env") # --- Provider-Stacks: nur Aufrufweg + Auth, KEINE Modelle ------------------------- PROVIDERS = { "claude": {"cli": "claude", "env_key": None}, # Auth via OAuth/~/.claude (Abo) "minimax": {"cli": "opencode", "env_key": "MINIMAX_API_KEY"}, } # LLM-Rollen des Scan-Boards. Jede braucht ROLLE_ in der .env. ROLLEN = ("schneiden", "nachfass", "sichten", "judge") def resolve_role(role: str) -> tuple[str, str]: """→ (provider, modell) aus ROLLE_=provider:modell. Fehlt die Konfiguration oder ist sie unbrauchbar: RuntimeError — NIE ein stiller Default (Leitprinzip 2).""" env_name = f"ROLLE_{role.upper()}" wert = os.getenv(env_name, "").strip() if not wert: raise RuntimeError( f"Rolle '{role}' hat kein Modell konfiguriert — {env_name}=provider:modell in .env setzen") provider, _, modell = wert.partition(":") if provider not in PROVIDERS: raise RuntimeError( f"{env_name}: unbekannter Provider '{provider}' (bekannt: {', '.join(PROVIDERS)})") if not modell: raise RuntimeError(f"{env_name}: Modell fehlt (Format provider:modell)") return provider, modell # --- Engine-Konstanten (Kanban + Agenten) ----------------------------------------- KANBAN_BATCH = int(os.getenv("KANBAN_BATCH", "4")) # Karten je Paket MAX_CARD_RETRIES = int(os.getenv("MAX_CARD_RETRIES", "3")) # dann dead-letter RETRY_BACKOFF = float(os.getenv("RETRY_BACKOFF", "30")) # Basis-Sekunden, exponentiell MAX_CONCURRENT_AGENTS = int(os.getenv("MAX_CONCURRENT_AGENTS", "6")) MAX_CONCURRENT_AGENTS_PER_TOPIC = int(os.getenv("MAX_CONCURRENT_AGENTS_PER_TOPIC", "6")) MAX_CONCURRENT_API_AGENTS = int(os.getenv("MAX_CONCURRENT_API_AGENTS", "12")) RAM_MIN_FREE_PCT = int(os.getenv("RAM_MIN_FREE_PCT", "20")) # 0 = RAM-Gate aus AGENT_TIMEOUT = int(os.getenv("AGENT_TIMEOUT", "360")) # Sekunden je LLM-Call