import os from pathlib import Path from typing import Any import yaml # type: ignore[import-untyped] from pydantic import BaseModel, Field class LLMConfig(BaseModel): endpoint: str = "http://localhost:11434" model: str = "codellama" timeout: int = 120 max_tokens: int = 2048 temperature: float = 0.3 class ReviewSettings(BaseModel): strictness: str = "balanced" max_issues_per_file: int = 20 syntax_highlighting: bool = True show_line_numbers: bool = True class LanguageConfig(BaseModel): enabled: bool = True review_rules: list[str] = Field(default_factory=list) max_line_length: int = 100 class Languages(BaseModel): python: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["pep8", "type-hints", "docstrings"])) javascript: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["airbnb"])) typescript: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["airbnb"])) go: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["golint", "staticcheck"])) rust: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["clippy"])) java: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["google-java"])) c: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["cppcheck"])) cpp: LanguageConfig = Field(default_factory=lambda: LanguageConfig(review_rules=["cppcheck"])) def get_language_config(self, language: str) -> LanguageConfig | None: return getattr(self, language.lower(), None) class StrictnessProfile(BaseModel): description: str = "" check_security: bool = True check_bugs: bool = True check_style: bool = True check_performance: bool = False check_documentation: bool = False min_severity: str = "info" class StrictnessProfiles(BaseModel): permissive: StrictnessProfile = Field(default_factory=lambda: StrictnessProfile( description="Focus on critical issues only", check_security=True, check_bugs=True, check_style=False, check_performance=False, check_documentation=False, min_severity="warning" )) balanced: StrictnessProfile = Field(default_factory=lambda: StrictnessProfile( description="Balanced review of common issues", check_security=True, check_bugs=True, check_style=True, check_performance=False, check_documentation=False, min_severity="info" )) strict: StrictnessProfile = Field(default_factory=lambda: StrictnessProfile( description="Comprehensive review of all issues", check_security=True, check_bugs=True, check_style=True, check_performance=True, check_documentation=True, min_severity="info" )) def get_profile(self, name: str) -> StrictnessProfile: return getattr(self, name.lower(), self.balanced) class HooksConfig(BaseModel): enabled: bool = True fail_on_critical: bool = True allow_bypass: bool = True class OutputConfig(BaseModel): format: str = "terminal" theme: str = "auto" show_suggestions: bool = True class LoggingConfig(BaseModel): level: str = "info" log_file: str = "" structured: bool = False class Config(BaseModel): llm: LLMConfig = Field(default_factory=LLMConfig) review: ReviewSettings = Field(default_factory=ReviewSettings) languages: Languages = Field(default_factory=Languages) strictness_profiles: StrictnessProfiles = Field(default_factory=StrictnessProfiles) hooks: HooksConfig = Field(default_factory=HooksConfig) output: OutputConfig = Field(default_factory=OutputConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) class ConfigLoader: def __init__(self, config_path: str | None = None): self.config_path = config_path self.global_config: Path | None = None self.project_config: Path | None = None def find_config_files(self) -> tuple[Path | None, Path | None]: env_config_path = os.environ.get("AICR_CONFIG_PATH") if env_config_path: env_path = Path(env_config_path) if env_path.exists(): return env_path, None self.global_config = Path.home() / ".aicr.yaml" self.project_config = Path.cwd() / ".aicr.yaml" if self.project_config.exists(): return self.project_config, self.global_config if self.global_config.exists(): return self.global_config, None return None, None def load(self) -> Config: config_path, global_path = self.find_config_files() config_data: dict[str, Any] = {} if global_path and global_path.exists(): with open(global_path) as f: global_data = yaml.safe_load(f) or {} config_data.update(global_data) if config_path and config_path.exists(): with open(config_path) as f: project_data = yaml.safe_load(f) or {} config_data.update(project_data) return Config(**config_data) def save(self, config: Config, path: Path) -> None: with open(path, "w") as f: yaml.dump(config.model_dump(), f, default_flow_style=False) def get_config(config_path: str | None = None) -> Config: loader = ConfigLoader(config_path) return loader.load()