Files
local-ai-commit-reviewer/src/config/config.py
7000pctAUTO a20a3c2b27
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
fix: resolve CI/CD type errors and workflow issues
2026-02-05 06:48:06 +00:00

165 lines
5.4 KiB
Python

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()