From 5f60710249a12a018b046acaefea8f5977924ddd Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 18:03:17 +0000 Subject: [PATCH] Add rules, fixes, and llm modules --- config_auditor/fixes.py | 163 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 config_auditor/fixes.py diff --git a/config_auditor/fixes.py b/config_auditor/fixes.py new file mode 100644 index 0000000..fb77332 --- /dev/null +++ b/config_auditor/fixes.py @@ -0,0 +1,163 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional +import json +import shutil + + +@dataclass +class Fix: + file: Path + description: str + original_content: str + fixed_content: str + reversible: bool = True + + +class FixerBase(ABC): + @property + @abstractmethod + def fix_id(self) -> str: + pass + + @abstractmethod + def can_fix(self, data: Dict[str, Any], file_path: Path) -> bool: + pass + + @abstractmethod + def apply_fix(self, data: Dict[str, Any], file_path: Path) -> Dict[str, Any]: + pass + + def revert_fix(self, original: str) -> str: + return original + + +class DeprecatedPackageFixer(FixerBase): + DEPRECATED_MAPPING = { + "request": "requests", + "urllib2": "urllib.request", + "httplib": "http.client", + } + + @property + def fix_id(self) -> str: + return "replace-deprecated-package" + + def can_fix(self, data: Dict[str, Any], file_path: Path) -> bool: + deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})} + return any(pkg in self.DEPRECATED_MAPPING for pkg in deps) + + def apply_fix(self, data: Dict[str, Any], file_path: Path) -> Dict[str, Any]: + for key in ["dependencies", "devDependencies"]: + if key in data: + for old_pkg, new_pkg in self.DEPRECATED_MAPPING.items(): + if old_pkg in data[key]: + data[key][new_pkg] = data[key].pop(old_pkg) + return data + + +class AddTestScriptFixer(FixerBase): + @property + def fix_id(self) -> str: + return "add-test-script" + + def can_fix(self, data: Dict[str, Any], file_path: Path) -> bool: + scripts = data.get("scripts", {}) + return "test" not in scripts + + def apply_fix(self, data: Dict[str, Any], file_path: Path) -> Dict[str, Any]: + if "scripts" not in data: + data["scripts"] = {} + if "test" not in data["scripts"]: + data["scripts"]["test"] = "echo 'Error: no test specified' && exit 1" + if "build" not in data["scripts"]: + data["scripts"]["build"] = "tsc" if file_path.name.endswith(".json") else "echo 'No build script'" + return data + + +class EnableStrictModeFixer(FixerBase): + @property + def fix_id(self) -> str: + return "enable-strict-mode" + + def can_fix(self, data: Dict[str, Any], file_path: Path) -> bool: + compiler_opts = data.get("compilerOptions", {}) + return compiler_opts.get("strict") is not True + + def apply_fix(self, data: Dict[str, Any], file_path: Path) -> Dict[str, Any]: + if "compilerOptions" not in data: + data["compilerOptions"] = {} + data["compilerOptions"]["strict"] = True + return data + + +class Fixer: + FIXERS = [ + DeprecatedPackageFixer(), + AddTestScriptFixer(), + EnableStrictModeFixer(), + ] + + def __init__(self, dry_run: bool = False, force: bool = False, confirm_func: Optional[Callable[[str], bool]] = None): + self.dry_run = dry_run + self.force = force + self.confirm_func = confirm_func + self.backup_dir = Path(".config_auditor_backup") + self.fixes_applied: List[Fix] = [] + + def _confirm(self, message: str) -> bool: + if self.confirm_func: + return self.confirm_func(message) + if self.force: + return True + return False + + def fix_config(self, file_path: Path, format_type: str, content: str) -> int: + from config_auditor.parsers import ParserFactory + + parser = ParserFactory() + data = parser.parse(format_type, content) + + if data is None: + return 0 + + original_content = content + fix_count = 0 + + for fixer in self.FIXERS: + if fixer.can_fix(data, file_path): + if not self.force and not self.dry_run: + if self._confirm(f"Apply fix for {fixer.fix_id}?"): + pass + else: + continue + + new_data = fixer.apply_fix(data, file_path) + fixed_content = parser.dump(format_type, new_data) + + if fixed_content: + if not self.dry_run: + if self.backup_dir.exists() is False: + self.backup_dir.mkdir(exist_ok=True) + backup_path = self.backup_dir / f"{file_path.name}.bak" + shutil.copy(file_path, backup_path) + file_path.write_text(fixed_content) + + self.fixes_applied.append(Fix( + file=file_path, + description=fixer.fix_id, + original_content=original_content, + fixed_content=fixed_content + )) + fix_count += 1 + + return fix_count + + def revert_last_fix(self): + if self.fixes_applied: + last_fix = self.fixes_applied.pop() + last_fix.file.write_text(last_fix.original_content) + + def get_fixes(self) -> List[Fix]: + return self.fixes_applied