diff --git a/-MechanicalMind-Dependency-AI-v3.0- b/-MechanicalMind-Dependency-AI-v3.0- new file mode 160000 index 00000000..263c8c47 --- /dev/null +++ b/-MechanicalMind-Dependency-AI-v3.0- @@ -0,0 +1 @@ +Subproject commit 263c8c4766664a164c39999679a84628d3b67145 diff --git a/.coverage b/.coverage new file mode 100644 index 00000000..2ccaa0e8 Binary files /dev/null and b/.coverage differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..7578bade --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI +on: push +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + - name: Run tests + run: | + pytest --cov=ai_core --cov-report=xml + - name: Upload coverage + uses: codecov/codecov-action@v4 diff --git a/.github/workflows/dependency_ai.yml b/.github/workflows/dependency_ai.yml index c1c4a355..8bc063d2 100755 --- a/.github/workflows/dependency_ai.yml +++ b/.github/workflows/dependency_ai.yml @@ -11,20 +11,20 @@ jobs: strategy: matrix: python-version: ["3.10", "3.11"] - + steps: - uses: actions/checkout@v4 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - + - name: Run Dependency Analyzer run: | python -m ai_core.dependency_analyzer --repo $GITHUB_REPOSITORY python -m ai_core.error_diagnosis_engine --log-path ./logs/ - + - name: Upload Results uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index ab5b13af..b9cc55ab 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -2,17 +2,17 @@ name: Dependency Scan on: push: - branches: - - main + branches: main pull_request: -jobs: +jobs: read scan: runs-on: ubuntu-latest steps: + jobs: witer - name: Checkout Code uses: actions/checkout@v3 - + - name: Dependency Scan uses: mechmind/ai-dependency-action@v3 with: diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 62bc9659..c421bc2e 100755 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -13,15 +13,15 @@ jobs: with: python-version: '3.10' cache: 'pip' - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - + - name: Debug - List Files run: ls -la - + - name: Run tests run: | python manage.py test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f1ef066..37135f89 100755 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,35 +1,24 @@ --- name: - uses: actions/download-artifact@v4.2.1 - with: - pattern: 'report-*.json' - path: ./custom_reports/ - merge-multiple: true - github-token: ${{ secrets.CI_CD_TOKEN }} - repository: mechmind-dwv/-MechanicalMind-Dependency-AI-v2.0- - run-id: ${{ github.event.workflow_run.id }} - on: - workflow_run: - workflows: ["Dependency Analysis"] - types: - - completed - - jobs: - build: - runs-on: ubuntu-latest +name: CI Pipeline + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - name: Upload artifact - uses: actions/upload-artifact@v3 + - name: Set up Python + uses: actions/setup-python@v4 with: - name: dependency-report - path: ./reports/ - env: - GITHUB_TOKEN: ${{ secrets.GH_ARTIFACT_ACCESS_TOKEN }} - - name: Process Artifacts + python-version: '3.10' + - name: Install dependencies run: | - echo "Processing downloaded artifacts..." - python ai_core/artifact_processor.py \ - --input "${{ runner.temp }}/artifacts" \ - --output ./reports/ -[![.github/workflows/main.yml](https://github.com/mechmind-dwv/-MechanicalMind-Dependency-AI-v2.0-/actions/workflows/main.yml/badge.svg)](https://github.com/mechmind-dwv/-MechanicalMind-Dependency-AI-v2.0-/actions/workflows/main.yml) + python -m pip install --upgrade pip + pip install -e . + - name: Run tests + run: | + python -m pytest diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index dd349688..53f5f35b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,12 +16,12 @@ jobs: steps: - uses: actions/checkout@v4 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - + - name: Cache pip dependencies uses: actions/cache@v3 with: @@ -29,23 +29,23 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }} restore-keys: | ${{ runner.os }}-pip- - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - + - name: Run linting run: | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics black --check --diff . - + - name: Run tests run: | pytest --cov=./ --cov-report=xml - + - name: Upload coverage uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/yml b/.github/workflows/yml index 1158088c..5cc13715 100644 --- a/.github/workflows/yml +++ b/.github/workflows/yml @@ -3,7 +3,7 @@ on: [push] jobs: check-version: - runs-on: + runs-on: group: ubuntu-runners labels: ubuntu-20.04-16core steps: diff --git a/.gitignore b/.gitignore index 5fbfa0d2..3259bc80 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ __pycache__/ *.egg-info/ venv/ *.log + +# Archivos temporales +filtered_reqs.txt + +# Entornos virtual +.venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8178ebec --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +- repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black diff --git a/README.md b/README.md index 717965c9..b3b8b6fb 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MechanicalMind Dependency AI v3.0 -![MechMind Logo](https://via.placeholder.com/150x50?text=MechMind+AI) +![MechMind Logo](https://via.placeholder.com/150x50?text=MechMind+AI) **Advanced Dependency Management with AI-Powered Resolution** ## 🚀 Key Features diff --git a/ai_core/auto_fix_module.py b/ai_core/auto_fix_module.py index 8e7d5f30..e6aec05b 100755 --- a/ai_core/auto_fix_module.py +++ b/ai_core/auto_fix_module.py @@ -8,13 +8,14 @@ from typing import Dict, List from pathlib import Path + class DependencyAutoFixer: def __init__(self, virtualenv_path: str = None): self.virtualenv_path = Path(virtualenv_path) if virtualenv_path else None self.fix_strategies = { "VERSION_NOT_FOUND": self._fix_version_not_found, "DISTRIBUTION_NOT_AVAILABLE": self._fix_distribution_unavailable, - "GIT_SSH_AUTH_ERROR": self._fix_git_ssh_error + "GIT_SSH_AUTH_ERROR": self._fix_git_ssh_error, } def apply_fix(self, error_type: str, context: Dict) -> Dict: @@ -26,75 +27,74 @@ def _fix_version_not_found(self, context: Dict) -> Dict: """Handle version not found errors""" package = context.get("package") required_version = context.get("required_version") - + # Get available versions available_versions = self._get_available_versions(package) if not available_versions: return {"status": "failed", "reason": "No versions available"} - + # Find closest compatible version compatible_version = self._find_compatible_version( - required_version, - available_versions + required_version, available_versions ) - + if not compatible_version: return {"status": "failed", "reason": "No compatible version found"} - + # Update requirements self._update_requirement_file( - context["requirement_file"], - package, - required_version, - compatible_version + context["requirement_file"], package, required_version, compatible_version ) - + return { "status": "success", "message": f"Updated {package} from {required_version} to {compatible_version}", "actions": [ f"Modified {context['requirement_file']}", - "Run 'pip install -r requirements.txt' to apply changes" - ] + "Run 'pip install -r requirements.txt' to apply changes", + ], } def _fix_git_ssh_error(self, context: Dict) -> Dict: """Convert SSH URLs to HTTPS""" cmd = [ - "git", "config", "--global", - "url.https://github.com/.insteadOf", "git@github.com:" + "git", + "config", + "--global", + "url.https://github.com/.insteadOf", + "git@github.com:", ] result = subprocess.run(cmd, capture_output=True, text=True) - + if result.returncode == 0: return { "status": "success", "message": "Converted Git SSH URLs to HTTPS", - "actions": ["Try installing dependencies again"] + "actions": ["Try installing dependencies again"], } else: return { "status": "failed", "reason": result.stderr, - "fallback": "Manually edit .gitconfig" + "fallback": "Manually edit .gitconfig", } def _create_clean_environment(self) -> Dict: """Create fresh virtual environment""" venv_path = self.virtualenv_path or Path("mechmind_clean_env") - + try: subprocess.run([sys.executable, "-m", "venv", str(venv_path)], check=True) return { "status": "success", "path": str(venv_path), - "activate_cmd": f"source {venv_path}/bin/activate" + "activate_cmd": f"source {venv_path}/bin/activate", } except subprocess.CalledProcessError as e: return { "status": "failed", "reason": str(e), - "suggestion": "Check Python venv module is available" + "suggestion": "Check Python venv module is available", } - # [...] (Additional fix methods) \ No newline at end of file + # [...] (Additional fix methods) diff --git a/ai_core/compatibility_matrix.py b/ai_core/compatibility_matrix.py old mode 100755 new mode 100644 index 4ad11895..744df7f8 --- a/ai_core/compatibility_matrix.py +++ b/ai_core/compatibility_matrix.py @@ -1,80 +1,63 @@ -""" -MechanicalMind Compatibility Matrix v2.0 -Package version compatibility database manager -""" - import sqlite3 -from typing import Dict, List from pathlib import Path + class CompatibilityMatrix: - def __init__(self, db_path: str = None): - self.db_path = db_path or str(Path(__file__).parent / 'knowledge_base' / 'version_compatibility.db') - self._init_db() - - def _init_db(self): - """Initialize database schema if not exists""" + def __init__(self, db_path: str = "knowledge_base/version_compatibility.db"): + self.db_path = db_path + self._initialize_db() + + def _initialize_db(self): + """Create database tables if they don't exist""" with sqlite3.connect(self.db_path) as conn: - cursor = conn.cursor() - cursor.execute(""" + conn.execute( + """ CREATE TABLE IF NOT EXISTS package_versions ( package TEXT NOT NULL, version TEXT NOT NULL, python_version TEXT NOT NULL, - status TEXT CHECK(status IN ('compatible', 'incompatible', 'untested')), + status TEXT NOT NULL, PRIMARY KEY (package, version, python_version) ) - """) - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS package_relations ( - parent_package TEXT NOT NULL, - parent_version TEXT NOT NULL, - child_package TEXT NOT NULL, - child_version_range TEXT NOT NULL, - relation_type TEXT CHECK(relation_type IN ('depends', 'conflicts')), - PRIMARY KEY (parent_package, parent_version, child_package) - ) - """) + """ + ) conn.commit() - def add_compatibility_record(self, package: str, version: str, - python_version: str, status: str) -> bool: + def _validate_input( + self, package: str, version: str, python_version: str, status: str + ) -> bool: + """Validate input parameters""" + if not all([package, version, python_version, status]): + return False + return True + + def add_compatibility_record( + self, package: str, version: str, python_version: str, status: str + ) -> bool: """Add or update compatibility record""" + if not self._validate_input(package, version, python_version, status): + return False + try: with sqlite3.connect(self.db_path) as conn: conn.execute( "INSERT OR REPLACE INTO package_versions VALUES (?, ?, ?, ?)", - (package, version, python_version, status) + (package, version, python_version, status), ) + conn.commit() return True except sqlite3.Error as e: print(f"Database error: {e}") return False - def check_compatibility(self, package: str, version: str, - python_version: str) -> str: - """Check compatibility status for a package version""" + def check_compatibility( + self, package: str, version: str, python_version: str + ) -> str: + """Check package compatibility status""" with sqlite3.connect(self.db_path) as conn: - cursor = conn.cursor() - cursor.execute(""" - SELECT status FROM package_versions - WHERE package = ? AND version = ? AND python_version = ? - """, (package, version, python_version)) - + cursor = conn.execute( + "SELECT status FROM package_versions WHERE package=? AND version=? AND python_version=?", + (package, version, python_version), + ) result = cursor.fetchone() - return result[0] if result else "untested" - - def find_compatible_versions(self, package: str, python_version: str) -> List[str]: - """Get all compatible versions for a package""" - with sqlite3.connect(self.db_path) as conn: - cursor = conn.cursor() - cursor.execute(""" - SELECT version FROM package_versions - WHERE package = ? AND python_version = ? AND status = 'compatible' - ORDER BY version DESC - """, (package, python_version)) - - return [row[0] for row in cursor.fetchall()] - - # [...] (Additional methods for relationship management) + return result[0] if result else "unknown" diff --git a/ai_core/dependency_analyzer.p b/ai_core/dependency_analyzer.p index 8b137891..e69de29b 100755 --- a/ai_core/dependency_analyzer.p +++ b/ai_core/dependency_analyzer.p @@ -1 +0,0 @@ - diff --git a/ai_core/dependency_analyzer.py b/ai_core/dependency_analyzer.py index 5a5179e6..dace9f6d 100755 --- a/ai_core/dependency_analyzer.py +++ b/ai_core/dependency_analyzer.py @@ -1,13 +1,15 @@ """ Módulo principal de análisis de dependencias - Versión Termux """ + from pathlib import Path import subprocess + class DependencyAnalyzer: def __init__(self): self.dependencies = [] - + def analyze(self, path): """Método principal de análisis""" print(f"Analizando dependencias en: {path}") @@ -16,8 +18,9 @@ def analyze(self, path): def _get_package_dependencies(self, package_name): """Obtiene dependencias de un paquete""" try: - result = subprocess.run(['pip', 'show', package_name], - capture_output=True, text=True) + result = subprocess.run( + ["pip", "show", package_name], capture_output=True, text=True + ) return result.stdout except Exception as e: return f"Error obteniendo dependencias: {str(e)}" @@ -26,13 +29,19 @@ def parse_requirements_txt(self, file_path): """Parsea archivos requirements.txt""" try: with open(file_path) as f: - return [line.strip() for line in f if line.strip() and not line.startswith('#')] + return [ + line.strip() + for line in f + if line.strip() and not line.startswith("#") + ] except Exception as e: print(f"Error leyendo archivo: {str(e)}") return [] + def main(): import argparse + parser = argparse.ArgumentParser() parser.add_argument("--path", required=True) args = parser.parse_args() @@ -41,5 +50,6 @@ def main(): result = analyzer.analyze(args.path) print(result) + if __name__ == "__main__": main() diff --git a/ai_core/error_diagnosis_engine.py b/ai_core/error_diagnosis_engine.py index cda49d45..8fca746f 100755 --- a/ai_core/error_diagnosis_engine.py +++ b/ai_core/error_diagnosis_engine.py @@ -1,5 +1,5 @@ """ -MechanicalMind Error Diagnosis Engine v2.0 +MechanicalMind Error Diagnosis Engine v3.0 Core module for error analysis and resolution """ @@ -11,39 +11,39 @@ # Configuración básica de logging logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) + class ErrorDiagnosisEngine: def __init__(self, knowledge_base_path: str = None): - self.logger = logging.getLogger('MechMind.Diagnosis') + self.logger = logging.getLogger("MechMind.Diagnosis") self.knowledge_base = self._load_knowledge_base(knowledge_base_path) self.error_patterns = { "DEPENDENCY_SYNTAX_ERROR": [ r"dependency_file_not_evaluatable", r"Invalid requirement:", - r"Parse error in requirements file" + r"Parse error in requirements file", ], "GIT_CONFIG_ERROR": [ r"git@github\.com", r"Permission denied \(publickey\)", - r"Could not read from remote repository" + r"Could not read from remote repository", ], "PYTHON_EXECUTION_FAILURE": [ r"exit 1", r"ModuleNotFoundError", - r"ImportError" - ] + r"ImportError", + ], } def _load_knowledge_base(self, path: str = None) -> Dict: """Carga la base de conocimiento con manejo de errores""" - default_path = Path(__file__).parent / 'knowledge_base' / 'common_errors.json' + default_path = Path(__file__).parent / "knowledge_base" / "common_errors.json" kb_path = Path(path) if path else default_path - + try: - with open(kb_path, 'r', encoding='utf-8') as f: + with open(kb_path, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: self.logger.error(f"Failed to load knowledge base: {e}") @@ -53,7 +53,7 @@ def classify_error(self, log_text: str) -> str: """Clasificación de errores con logging""" if not log_text: return "EMPTY_ERROR" - + log_text = log_text.lower() for error_type, patterns in self.error_patterns.items(): for pattern in patterns: @@ -67,10 +67,9 @@ def classify_error(self, log_text: str) -> str: def get_solutions(self, error_type: str) -> List[str]: """Obtiene soluciones con fallback seguro""" - return self.knowledge_base.get(error_type, {}).get("solutions", [ - "Check system documentation", - "Contact support team" - ]) + return self.knowledge_base.get(error_type, {}).get( + "solutions", ["Check system documentation", "Contact support team"] + ) def full_diagnosis(self, error_log: str) -> Dict: """Diagnóstico completo con metadatos""" @@ -79,9 +78,10 @@ def full_diagnosis(self, error_log: str) -> Dict: "error_type": error_type, "solutions": self.get_solutions(error_type), "confidence": 0.9 if error_type != "UNKNOWN_ERROR" else 0.3, - "timestamp": datetime.datetime.now().isoformat() + "timestamp": datetime.datetime.now().isoformat(), } + if __name__ == "__main__": engine = ErrorDiagnosisEngine() test_error = "ERROR: dependency_file_not_evaluatable in requirements.txt" diff --git a/ai_core/knowledge_base/__init__.py b/ai_core/knowledge_base/__init__.py index a1092feb..6155f519 100755 --- a/ai_core/knowledge_base/__init__.py +++ b/ai_core/knowledge_base/__init__.py @@ -1,19 +1,53 @@ """ -MechanicalMind Knowledge Base Package v2.0 -Initialization for knowledge base modules +Módulo de Base de Conocimiento - Versión Corregida +================================================= """ -from pathlib import Path import json +from pathlib import Path +from typing import Dict, Any, Optional + + +def load_error_knowledge(file_path: Optional[str] = None) -> Dict[str, Any]: + """ + Carga la base de conocimiento desde un archivo JSON. + + Args: + file_path: Ruta opcional al archivo JSON. Por defecto usa 'common_errors.json' + en el directorio del módulo. + + Returns: + Diccionario con la estructura {common_errors: {error1: details, ...}} -def load_error_knowledge() -> dict: - """Load error knowledge base from JSON""" - kb_path = Path(__file__).parent / 'common_errors.json' + Raises: + ImportError: Si hay problemas al cargar el archivo + ValueError: Si la estructura no es válida + """ try: - with open(kb_path, 'r') as f: - return json.load(f) + # Ruta por defecto si no se especifica + kb_path = ( + Path(file_path) + if file_path + else Path(__file__).parent / "common_errors.json" + ) + + with open(kb_path, "r", encoding="utf-8") as f: + data = json.load(f) + + # Validación de estructura + if not isinstance(data, dict) or "common_errors" not in data: + raise ValueError( + "La estructura debe contener 'common_errors' como clave principal" + ) + + return data + + except FileNotFoundError as e: + raise ImportError(f"Archivo no encontrado: {kb_path}") from e + except json.JSONDecodeError as e: + raise ImportError(f"Error decodificando JSON: {str(e)}") from e except Exception as e: - raise ImportError(f"Failed to load knowledge base: {str(e)}") + raise ImportError(f"Error cargando base de conocimiento: {str(e)}") from e + -# Initialize on package import -error_knowledge = load_error_knowledge() +# Eliminado: No más carga automática al importar diff --git a/ai_core/knowledge_base/common_errors.json b/ai_core/knowledge_base/common_errors.json index 7069f249..a9191e6f 100755 --- a/ai_core/knowledge_base/common_errors.json +++ b/ai_core/knowledge_base/common_errors.json @@ -1,39 +1,10 @@ { - "DEPENDENCY_SYNTAX_ERROR": { - "description": "Invalid syntax in dependency specification file", - "patterns": [ - "Invalid requirement:", - "Parse error in requirements file" - ], - "solutions": [ - "Run 'pip install -r requirements.txt --dry-run' to validate syntax", - "Check for special characters or malformed version specifiers" - ], - "severity": "high" - }, - "VERSION_NOT_FOUND": { - "description": "Requested package version is not available", - "patterns": [ - "could not find a version that satisfies", - "no matching distribution found" - ], - "solutions": [ - "Check available versions with 'pip index versions '", - "Remove version constraint or specify different version" - ], - "severity": "high" - }, - "GIT_SSH_AUTH_ERROR": { - "description": "Authentication failure with Git SSH URLs", - "patterns": [ - "git@github.com: permission denied", - "could not read from remote repository" - ], - "solutions": [ - "Run: git config --global url.https://github.com/.insteadOf git@github.com:", - "Set up SSH keys properly", - "Use HTTPS URLs instead of SSH" - ], - "severity": "medium" - } + "common_errors": { + "DEFAULT_ERROR": { + "description": "Error por defecto - reemplazar con datos reales", + "patterns": ["error"], + "solutions": ["Consultar documentación"], + "severity": "medium" + } + } } diff --git a/ai_core/knowledge_base/version_compatibility.db b/ai_core/knowledge_base/version_compatibility.db index 506b728b..45ed27ac 100755 Binary files a/ai_core/knowledge_base/version_compatibility.db and b/ai_core/knowledge_base/version_compatibility.db differ diff --git a/ai_core/utils /file_helpers.py b/ai_core/utils /file_helpers.py index e1f22efa..a046abba 100755 --- a/ai_core/utils /file_helpers.py +++ b/ai_core/utils /file_helpers.py @@ -8,17 +8,19 @@ import json import hashlib -def safe_read_file(file_path: str or Path, encoding: str = 'utf-8') -> str: + +def safe_read_file(file_path: str or Path, encoding: str = "utf-8") -> str: """Read file with error handling""" try: - with open(file_path, 'r', encoding=encoding) as f: + with open(file_path, "r", encoding=encoding) as f: return f.read() except UnicodeDecodeError: - with open(file_path, 'r', encoding='latin-1') as f: + with open(file_path, "r", encoding="latin-1") as f: return f.read() except Exception as e: raise IOError(f"Failed to read {file_path}: {str(e)}") + def read_yaml(file_path: str or Path) -> dict: """Safely read YAML file""" content = safe_read_file(file_path) @@ -27,23 +29,26 @@ def read_yaml(file_path: str or Path) -> dict: except yaml.YAMLError as e: raise ValueError(f"Invalid YAML in {file_path}: {str(e)}") + def write_json(data: dict, file_path: str or Path, indent: int = 2) -> bool: """Write data to JSON file""" try: - with open(file_path, 'w') as f: + with open(file_path, "w") as f: json.dump(data, f, indent=indent) return True except Exception as e: raise IOError(f"Failed to write JSON to {file_path}: {str(e)}") -def file_checksum(file_path: str or Path, algorithm: str = 'sha256') -> str: + +def file_checksum(file_path: str or Path, algorithm: str = "sha256") -> str: """Calculate file checksum""" hash_func = getattr(hashlib, algorithm)() - with open(file_path, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b''): + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): hash_func.update(chunk) return hash_func.hexdigest() + def find_files_by_pattern(directory: str or Path, pattern: str) -> list: """Find files matching pattern recursively""" path = Path(directory) diff --git a/ai_core/utils /logging_config.py b/ai_core/utils /logging_config.py index 64866df1..d2563ffa 100755 --- a/ai_core/utils /logging_config.py +++ b/ai_core/utils /logging_config.py @@ -8,42 +8,47 @@ import sys from typing import Optional -def configure_logging(log_file: Optional[str or Path] = None, - level: str = 'INFO') -> logging.Logger: + +def configure_logging( + log_file: Optional[str or Path] = None, level: str = "INFO" +) -> logging.Logger: """Configure consistent logging for all modules""" - + log_level = getattr(logging, level.upper(), logging.INFO) - - logger = logging.getLogger('mechmind') + + logger = logging.getLogger("mechmind") logger.setLevel(log_level) - + # Clear existing handlers for handler in logger.handlers[:]: logger.removeHandler(handler) - + # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(log_level) console_format = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) console_handler.setFormatter(console_format) logger.addHandler(console_handler) - + # File handler if specified if log_file: file_handler = logging.FileHandler(log_file) file_handler.setLevel(log_level) file_format = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s') + "%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s" + ) file_handler.setFormatter(file_format) logger.addHandler(file_handler) - + # Configure third-party loggers - for lib in ['urllib3', 'git', 'requests']: + for lib in ["urllib3", "git", "requests"]: logging.getLogger(lib).setLevel(logging.WARNING) - + return logger + def get_module_logger(module_name: str) -> logging.Logger: """Get pre-configured logger for a module""" - return logging.getLogger(f'mechmind.{module_name}') + return logging.getLogger(f"mechmind.{module_name}") diff --git a/ai_core/utils /network_helpers.py b/ai_core/utils /network_helpers.py index 21f9ec0f..1f1e78ed 100755 --- a/ai_core/utils /network_helpers.py +++ b/ai_core/utils /network_helpers.py @@ -9,27 +9,29 @@ from pathlib import Path import socket + def check_internet_connection(timeout: float = 3.0) -> bool: """Check if internet connection is available""" try: - requests.get('https://pypi.org/simple/', timeout=timeout) + requests.get("https://pypi.org/simple/", timeout=timeout) return True except (requests.ConnectionError, requests.Timeout): return False -def download_file(url: str, destination: str or Path, - timeout: float = 30.0) -> bool: + +def download_file(url: str, destination: str or Path, timeout: float = 30.0) -> bool: """Download file from URL with progress and retries""" try: with requests.get(url, stream=True, timeout=timeout) as r: r.raise_for_status() - with open(destination, 'wb') as f: + with open(destination, "wb") as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) return True except Exception as e: raise IOError(f"Download failed: {str(e)}") + def get_pypi_package_info(package_name: str) -> Optional[dict]: """Fetch package metadata from PyPI""" url = f"https://pypi.org/pypi/{package_name}/json" @@ -44,7 +46,8 @@ def get_pypi_package_info(package_name: str) -> Optional[dict]: except Exception as e: raise ConnectionError(f"PyPI API error: {str(e)}") -def is_port_available(port: int, host: str = 'localhost') -> bool: + +def is_port_available(port: int, host: str = "localhost") -> bool: """Check if network port is available""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: diff --git a/ai_core/utils/__init__.py b/ai_core/utils/__init__.py index 34f564aa..7323acd7 100755 --- a/ai_core/utils/__init__.py +++ b/ai_core/utils/__init__.py @@ -1,24 +1,22 @@ """ -MechanicalMind Utilities Package v2.0 -Utility functions for the Dependency AI system +Módulo utils para MechanicalMind Dependency AI """ -from .file_helpers import ( - safe_read_file, - read_yaml, - write_json, - file_checksum, - find_files_by_pattern -) -from .network_helpers import ( - check_internet_connection, - download_file, - get_pypi_package_info, - is_port_available -) -from .logging_config import ( - configure_logging, - get_module_logger -) - -__all__ = [name for name in globals() if not name.startswith("_")] \ No newline at end of file +__version__ = "3.0.1" +__author__ = "MechMind Team " + + +# Importaciones diferidas para evitar circular imports +def configure_logging(): + from .logging_config import configure_logging as _configure + + return _configure() + + +def make_http_request(*args, **kwargs): + from .network_helpers import make_http_request as _make_request + + return _make_request(*args, **kwargs) + + +__all__ = ["__version__", "__author__", "configure_logging", "make_http_request"] diff --git a/ai_core/utils/file_helpers.py b/ai_core/utils/file_helpers.py new file mode 100644 index 00000000..0d422607 --- /dev/null +++ b/ai_core/utils/file_helpers.py @@ -0,0 +1,234 @@ +""" +File System Utilities Module +--------------------------- + +Provides robust file operations for MechanicalMind AI Dependency System v3.1.0 + +Key Features: +- Safe file operations with comprehensive error handling +- Atomic write operations to prevent corruption +- Support for multiple file formats (JSON, YAML, text) +- Filesystem abstraction for cross-platform compatibility +""" + +import os +import json +import yaml +import tempfile +import hashlib +from pathlib import Path +from typing import Any, Optional, Union, List +import logging +from functools import wraps + +_logger = logging.getLogger(__name__) + + +def handle_file_errors(func): + """Decorator to standardize file operation error handling""" + + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except FileNotFoundError as e: + _logger.error(f"File not found: {str(e)}") + raise + except PermissionError as e: + _logger.error(f"Permission denied: {str(e)}") + raise + except Exception as e: + _logger.error(f"Unexpected error in {func.__name__}: {str(e)}") + raise + + return wrapper + + +@handle_file_errors +def safe_read_file( + file_path: Union[str, Path], encoding: str = "utf-8" +) -> Optional[str]: + """ + Safely read file content with comprehensive error handling + + Args: + file_path: Path to the file to read + encoding: Text encoding to use (default: utf-8) + + Returns: + File content as string or None if read fails + """ + try: + with open(file_path, "r", encoding=encoding) as f: + return f.read() + except Exception as e: + _logger.warning(f"Failed to read {file_path}: {str(e)}") + return None + + +@handle_file_errors +def atomic_write_file( + file_path: Union[str, Path], content: str, encoding: str = "utf-8" +) -> bool: + """ + Atomically write content to a file using temp file replacement + + Args: + file_path: Destination file path + content: Content to write + encoding: Text encoding to use + + Returns: + True if write succeeded, False otherwise + """ + temp_file = None + try: + file_path = Path(file_path) + with tempfile.NamedTemporaryFile( + mode="w", encoding=encoding, dir=file_path.parent, delete=False + ) as temp_file: + temp_file.write(content) + temp_path = Path(temp_file.name) + + temp_path.replace(file_path) + return True + except Exception as e: + _logger.error(f"Atomic write failed for {file_path}: {str(e)}") + if temp_file: + try: + Path(temp_file.name).unlink(missing_ok=True) + except Exception: + pass + return False + + +@handle_file_errors +def read_json_file(file_path: Union[str, Path]) -> Optional[dict]: + """ + Safely read and parse JSON file + + Args: + file_path: Path to JSON file + + Returns: + Parsed JSON data or None if failed + """ + content = safe_read_file(file_path) + if content is None: + return None + + try: + return json.loads(content) + except json.JSONDecodeError as e: + _logger.error(f"Invalid JSON in {file_path}: {str(e)}") + return None + + +@handle_file_errors +def write_json_file(file_path: Union[str, Path], data: Any, indent: int = 2) -> bool: + """ + Safely write data to JSON file with atomic operation + + Args: + file_path: Destination file path + data: Data to serialize + indent: JSON indentation level + + Returns: + True if write succeeded + """ + try: + content = json.dumps(data, indent=indent) + return atomic_write_file(file_path, content) + except Exception as e: + _logger.error(f"JSON serialization failed: {str(e)}") + return False + + +@handle_file_errors +def read_yaml_file(file_path: Union[str, Path]) -> Optional[dict]: + """ + Safely read and parse YAML file + + Args: + file_path: Path to YAML file + + Returns: + Parsed YAML data or None if failed + """ + content = safe_read_file(file_path) + if content is None: + return None + + try: + return yaml.safe_load(content) + except yaml.YAMLError as e: + _logger.error(f"Invalid YAML in {file_path}: {str(e)}") + return None + + +@handle_file_errors +def write_yaml_file(file_path: Union[str, Path], data: Any) -> bool: + """ + Safely write data to YAML file with atomic operation + + Args: + file_path: Destination file path + data: Data to serialize + + Returns: + True if write succeeded + """ + try: + content = yaml.dump(data) + return atomic_write_file(file_path, content) + except Exception as e: + _logger.error(f"YAML serialization failed: {str(e)}") + return False + + +@handle_file_errors +def file_checksum( + file_path: Union[str, Path], algorithm: str = "sha256" +) -> Optional[str]: + """ + Calculate file checksum + + Args: + file_path: Path to file + algorithm: Hash algorithm (md5, sha1, sha256) + + Returns: + Hex digest string or None if failed + """ + try: + hash_func = getattr(hashlib, algorithm)() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_func.update(chunk) + return hash_func.hexdigest() + except Exception as e: + _logger.error(f"Checksum failed for {file_path}: {str(e)}") + return None + + +@handle_file_errors +def find_files_by_pattern( + directory: Union[str, Path], pattern: str = "*.py" +) -> List[Path]: + """ + Find files matching pattern in directory + + Args: + directory: Root directory to search + pattern: Glob pattern to match + + Returns: + List of matching file paths + """ + directory = Path(directory) + if not directory.is_dir(): + _logger.warning(f"Invalid directory: {directory}") + return [] + + return list(directory.rglob(pattern)) diff --git a/ai_core/utils/logging_config.py b/ai_core/utils/logging_config.py new file mode 100644 index 00000000..8ba76a8c --- /dev/null +++ b/ai_core/utils/logging_config.py @@ -0,0 +1,40 @@ +# ai_core/utils/logging_config.py +import logging +from pathlib import Path +from typing import Optional + + +def setup_logging( + log_file: str = "mechmind.log", level: int = logging.INFO +) -> logging.Logger: + """Configura logging básico para la aplicación""" + log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + logging.basicConfig( + level=level, + format=log_format, + handlers=[logging.FileHandler(log_file), logging.StreamHandler()], + ) + return logging.getLogger(__name__) + + +def configure_logging(config_path: Optional[str] = None) -> logging.Logger: + """Configuración avanzada de logging desde archivo""" + if config_path and Path(config_path).exists(): + try: + with open(config_path) as f: + import json + + config = json.load(f) + logging.config.dictConfig(config) + except Exception as e: + print(f"Error loading logging config: {e}") + return setup_logging() + return setup_logging() + + +def get_module_logger(name: str) -> logging.Logger: + """Obtiene un logger configurado para un módulo específico""" + logger = logging.getLogger(name) + if not logger.handlers: + setup_logging() + return logger diff --git a/ai_core/utils/network_helpers.py b/ai_core/utils/network_helpers.py new file mode 100644 index 00000000..4fd287b8 --- /dev/null +++ b/ai_core/utils/network_helpers.py @@ -0,0 +1,50 @@ +import requests +from typing import Optional, Dict, Any +from urllib.parse import urlparse + + +def fetch_url(url: str, timeout: int = 10) -> Optional[Dict[str, Any]]: + """Fetch data from a URL with error handling""" + try: + response = requests.get(url, timeout=timeout) + response.raise_for_status() + return response.json() + except Exception as e: + print(f"Error fetching {url}: {e}") + return None + + +def check_internet_connection(test_url: str = "https://www.google.com") -> bool: + """Check if internet connection is available""" + try: + requests.get(test_url, timeout=5) + return True + except: + return False + + +def download_file(url: str, save_path: str) -> bool: + """Download a file from URL to local path""" + try: + response = requests.get(url, stream=True) + response.raise_for_status() + with open(save_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + return True + except Exception as e: + print(f"Download failed: {e}") + return False + + +def get_pypi_package_info(package_name: str) -> Optional[Dict[str, Any]]: + """Get package info from PyPI""" + return fetch_url(f"https://pypi.org/pypi/{package_name}/json") + + +def is_port_available(host: str, port: int) -> bool: + """Check if a network port is available""" + import socket + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex((host, port)) != 0 diff --git a/diagnose.py b/diagnose.py index 45825cde..f1ff6f89 100644 --- a/diagnose.py +++ b/diagnose.py @@ -5,10 +5,13 @@ print(f"Python path: {sys.path}") print(f"Directorio actual: {Path.cwd()}") print(f"¿Existe ai_core?: {(Path('ai_core') / '__init__.py').exists()}") -print(f"¿Existe dependency_analyzer?: {(Path('ai_core') / 'dependency_analyzer.py').exists()}") +print( + f"¿Existe dependency_analyzer?: {(Path('ai_core') / 'dependency_analyzer.py').exists()}" +) try: from ai_core.dependency_analyzer import DependencyAnalyzer + print("✅ Importación exitosa") except Exception as e: print(f"❌ Error de importación: {str(e)}") diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..75c0a6f7 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,6 @@ +# Contenido sugerido: +""" +## Estructura del Proyecto +- ai_core/: Módulos principales +- tests/: Pruebas unitarias +- scripts/: Utilidades de despliegue diff --git a/docs/Filesystem b/docs/Filesystem index 2f67a424..b27d1a83 100755 --- a/docs/Filesystem +++ b/docs/Filesystem @@ -1,25 +1,25 @@ -## **📁 Estructura del Proyecto (Filesystem)** +## **📁 Estructura del Proyecto (Filesystem)** ```bash -mechbot-2x/ -├── ai_core/ -│ ├── dependency_analyzer.py # Lógica principal de análisis de dependencias -│ ├── error_diagnosis_engine.py # Motor de diagnóstico de errores -│ ├── auto_fix_module.py # Módulo de autocorrección -│ └── knowledge_base/ -│ ├── version_compatibility.db # Base de datos de compatibilidad -│ └── common_errors.json # Errores frecuentes y soluciones -├── config/ -│ ├── env_vars.yaml # Variables de entorno -│ └── execution_profiles.yaml # Perfiles de ejecución (dev, prod, testing) -├── logs/ -│ ├── dependency_errors/ # Logs de fallos en dependencias -│ └── execution_traces/ # Trazas de ejecución de la IA -├── tests/ -│ ├── integration_tests/ # Pruebas de integración -│ └── unit_tests/ # Pruebas unitarias -├── scripts/ -│ ├── setup_environment.sh # Script de configuración inicial -│ └── auto_deploy.py # Despliegue automático en CI/CD -└── README.md # Documentación del proyecto +mechbot-2x/ +├── ai_core/ +│ ├── dependency_analyzer.py # Lógica principal de análisis de dependencias +│ ├── error_diagnosis_engine.py # Motor de diagnóstico de errores +│ ├── auto_fix_module.py # Módulo de autocorrección +│ └── knowledge_base/ +│ ├── version_compatibility.db # Base de datos de compatibilidad +│ └── common_errors.json # Errores frecuentes y soluciones +├── config/ +│ ├── env_vars.yaml # Variables de entorno +│ └── execution_profiles.yaml # Perfiles de ejecución (dev, prod, testing) +├── logs/ +│ ├── dependency_errors/ # Logs de fallos en dependencias +│ └── execution_traces/ # Trazas de ejecución de la IA +├── tests/ +│ ├── integration_tests/ # Pruebas de integración +│ └── unit_tests/ # Pruebas unitarias +├── scripts/ +│ ├── setup_environment.sh # Script de configuración inicial +│ └── auto_deploy.py # Despliegue automático en CI/CD +└── README.md # Documentación del proyecto ``` diff --git a/docs/Roadmap b/docs/Roadmap index 144bd867..66b349f1 100755 --- a/docs/Roadmap +++ b/docs/Roadmap @@ -1,4 +1,4 @@ -## **📌 Próximos Pasos (Roadmap)** +## **📌 Próximos Pasos (Roadmap)** | Feature | Prioridad | Responsable | ETA | |----------------------------|----------|--------------|-----------| diff --git a/docs/SECURITY.md b/docs/SECURITY.md index f6140781..c81a3ec3 100755 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -1,4 +1,4 @@ -## **🛡 Versiones Soportadas** +## **🛡 Versiones Soportadas** | Versión | Soporte | Hasta | |---------|--------------------|--------------| @@ -6,14 +6,14 @@ | 2.2.x | ⚠️ Solo críticas | 31/12/2025 | | ≤ 2.1 | ❌ Sin soporte | N/A | -**Notas:** -- Recibirás parches automáticos si usas `main` (v2.3.x) -- Las versiones antiguas requieren actualización +**Notas:** +- Recibirás parches automáticos si usas `main` (v2.3.x) +- Las versiones antiguas requieren actualización --- -## **🚨 Reportar Vulnerabilidades** -### **1. Métodos Seguros** +## **🚨 Reportar Vulnerabilidades** +### **1. Métodos Seguros** ```bash # Usando nuestro script (preferido) ./scripts/reportar_vulnerabilidad.sh --tipo "inyección SQL" diff --git a/docs/SECURITY_POLICY.md b/docs/SECURITY_POLICY.md index b8726903..915b6a31 100755 --- a/docs/SECURITY_POLICY.md +++ b/docs/SECURITY_POLICY.md @@ -10,4 +10,4 @@ 3. **Monitorización:** - Alertas en CloudWatch para actividad anómala - - Logs de ejecución cifrados en S3 \ No newline at end of file + - Logs de ejecución cifrados en S3 diff --git a/docs/SEGURITY/Auto-Hospedado.md b/docs/SEGURITY/Auto-Hospedado.md index 0cf9255c..7b6c1ab2 100755 --- a/docs/SEGURITY/Auto-Hospedado.md +++ b/docs/SEGURITY/Auto-Hospedado.md @@ -64,20 +64,20 @@ jobs: strategy: matrix: python-version: ["3.10", "3.11"] - + steps: - uses: actions/checkout@v4 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - + - name: Run Dependency Analyzer run: | python -m ai_core.dependency_analyzer --repo $GITHUB_REPOSITORY python -m ai_core.error_diagnosis_engine --log-path ./logs/ - + - name: Upload Results uses: actions/upload-artifact@v3 with: @@ -127,7 +127,7 @@ aws cloudwatch put-metric-alarm \ --namespace "GitHubRunners" \ --threshold 80 \ --comparison-operator GreaterThanThreshold - + ### **4. Automatización de Mantenimiento** **Archivo:** `.github/workflows/runner-cleanup.yml` @@ -181,6 +181,6 @@ jobs: --- -**✅ Firmado:** -**El Arquitecto DevOps** 🔧 +**✅ Firmado:** +**El Arquitecto DevOps** 🔧 *"Construyendo la infraestructura del futuro, hoy."* diff --git a/docs/architectural/diagram b/docs/architectural/diagram/Overview.mmd similarity index 97% rename from docs/architectural/diagram rename to docs/architectural/diagram/Overview.mmd index 58843973..d090b552 100644 --- a/docs/architectural/diagram +++ b/docs/architectural/diagram/Overview.mmd @@ -1,4 +1,4 @@ -graph TD + graph A[CLI Interface] --> B[Core Engine] B --> C[Analysis Module] B --> D[Resolution Module] diff --git a/docs/requeriments.docs.txt b/docs/requeriments.docs.txt index 8615dcc9..f1e6c97a 100644 --- a/docs/requeriments.docs.txt +++ b/docs/requeriments.docs.txt @@ -1,2 +1,2 @@ sphinx>=5.0.0 -furo>=2022.4.0 \ No newline at end of file +furo>=2022.4.0 diff --git a/error_diagnosis_engine.py b/error_diagnosis_engine.py index 705595ab..8a5fc063 100755 --- a/error_diagnosis_engine.py +++ b/error_diagnosis_engine.py @@ -9,18 +9,19 @@ import json from pathlib import Path + class ErrorDiagnosisEngine: def __init__(self, knowledge_base_path: str = None): self.knowledge_base = self._load_knowledge_base(knowledge_base_path) - self.logger = logging.getLogger('ErrorDiagnosis') - + self.logger = logging.getLogger("ErrorDiagnosis") + def _load_knowledge_base(self, path: str = None) -> Dict: """Load error patterns and solutions""" - default_path = Path(__file__).parent / 'knowledge_base' / 'common_errors.json' + default_path = Path(__file__).parent / "knowledge_base" / "common_errors.json" kb_path = Path(path) if path else default_path - + try: - with open(kb_path, 'r') as f: + with open(kb_path, "r") as f: return json.load(f) except Exception as e: self.logger.error(f"Failed to load knowledge base: {e}") @@ -30,24 +31,24 @@ def diagnose(self, log_text: str) -> Dict: """Main diagnosis entry point""" error_type = self._classify_error(log_text) solution = self._find_solution(error_type, log_text) - + return { "error_type": error_type, "solution": solution, "confidence": self._calculate_confidence(log_text, error_type), - "related_errors": self._find_related_errors(error_type) + "related_errors": self._find_related_errors(error_type), } def _classify_error(self, log_text: str) -> str: """Classify error based on patterns""" log_text = log_text.lower() - + # Check against known patterns for error_type, data in self.knowledge_base.items(): for pattern in data.get("patterns", []): if re.search(pattern, log_text): return error_type - + # Heuristic matching if "could not find a version" in log_text: return "VERSION_NOT_FOUND" @@ -55,7 +56,7 @@ def _classify_error(self, log_text: str) -> str: return "DISTRIBUTION_NOT_AVAILABLE" elif "git@github.com" in log_text and "permission denied" in log_text: return "GIT_SSH_AUTH_ERROR" - + return "UNKNOWN_ERROR" def _find_solution(self, error_type: str, log_text: str) -> List[str]: @@ -63,12 +64,12 @@ def _find_solution(self, error_type: str, log_text: str) -> List[str]: # First try exact matches if error_type in self.knowledge_base: return self.knowledge_base[error_type].get("solutions", []) - + # Fallback to generic solutions return [ "Check network connectivity", "Verify package exists in repository", - "Try with different version specifiers" + "Try with different version specifiers", ] - # [...] (Additional diagnostic methods) \ No newline at end of file + # [...] (Additional diagnostic methods) diff --git a/manage.py b/manage.py index 352ef557..21af8c67 100755 --- a/manage.py +++ b/manage.py @@ -2,9 +2,10 @@ import os import sys + def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -15,5 +16,6 @@ def main(): ) from exc execute_from_command_line(sys.argv) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/mechmind_cli.py b/mechmind_cli.py index 9ebeaca5..68c149bd 100755 --- a/mechmind_cli.py +++ b/mechmind_cli.py @@ -3,25 +3,27 @@ from ai_core import ErrorDiagnosisEngine import datetime + @click.command() -@click.argument('error_log') +@click.argument("error_log") def diagnose(error_log): """CLI para diagnóstico de errores de dependencias""" try: engine = ErrorDiagnosisEngine() result = engine.full_diagnosis(error_log) - + click.echo("\n=== Diagnóstico MechMind AI ===") click.echo(f"📌 Error: {error_log[:100]}...") click.echo(f"🔍 Tipo: {result['error_type']}") click.echo(f"🔄 Confianza: {result['confidence']*100:.1f}%") click.echo("\n💡 Soluciones recomendadas:") - for i, sol in enumerate(result['solutions'], 1): + for i, sol in enumerate(result["solutions"], 1): click.echo(f" {i}. {sol}") click.echo(f"\n⏰ Timestamp: {result['timestamp']}") - click.echo("="*40) + click.echo("=" * 40) except Exception as e: click.echo(f"❌ Error en el diagnóstico: {str(e)}", err=True) -if __name__ == '__main__': + +if __name__ == "__main__": diagnose() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..f3d32603 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" + +[project] +name = "MechanicalMind-Dependency-AI" +version = "3.0.1" +authors = [ + {name = "MechMind Team", email = "dev@mechmind.example"} +] +description = "Dependency analysis system for mechanical projects" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "requests>=2.25.1", + "pyyaml>=6.0", + "sqlalchemy>=1.4.0", + "click>=8.0.0", + "django>=4.0,<5.0" +] + +[project.optional-dependencies] +dev = ["black>=25.0", "pre-commit>=3.0"] + +[tool.black] +line-length = 88 +target-version = ['py310'] diff --git a/requirements-dev.txt b/requirements-dev.txt index 49860f50..45230214 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,4 +12,4 @@ flake8>=4.0.0 # Documentation sphinx>=5.0.0 -furo>=2022.4.0 \ No newline at end of file +furo>=2022.4.0 diff --git a/requirements-test.txt b/requirements-test.txt index 38fad7e4..9a2791e8 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,3 @@ pytest>=7.0.0 pytest-django>=4.5.0 -factory-boy>=3.0.0 \ No newline at end of file +factory-boy>=3.0.0 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 index cadff327..c486d76c --- a/requirements.txt +++ b/requirements.txt @@ -1,39 +1,41 @@ -# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements -# Last updated: 2024-06-20 - -# HTTP Requests -requests>=2.25.1 # Modern HTTP client with SSL verification - -# Configuration -pyyaml>=6.0 # YAML parser for configuration files - -# Database ORM -sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support - -# CLI Framework -click>=8.0.0 # Command Line Interface creation toolkit - -# Web Framework -django>=4.0,<5.0 # Full-stack web framework (LTS version) - -# Local Package (install in editable mode) --e . # Installs your myproject package from current directory# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements -# Last updated: 2024-04-12 - -# HTTP Requests -requests>=2.25.1 # Modern HTTP client with SSL verification - -# Configuration -pyyaml>=6.0 # YAML parser for configuration files - -# Database ORM -sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support - -# CLI Framework -click>=8.0.0 # Command Line Interface creation toolkit - -# Web Framework -django>=4.0,<5.0 # Full-stack web framework (LTS version) - -# Local Package (install in editable mode) --e . # Installs your myproject package from current directory \ No newline at end of file +# MechanicalMind Dependency AI - Production Requirements +# Version: 3.0.1 | LTS Release: 2024-Q3 +# Minimum Python: 3.10 +# Security Level: Enterprise + +### CORE INFRASTRUCTURE ### +requests>=2.31.0 # HTTP/2 client with: + # - Brotli/Zstd compression + # - DNS over HTTPS + # - Circuit breaker pattern + +pyyaml>=6.0.1 # YAML processor with: + # - SafeLoader enforcement + # - Custom tag resolution + # - Round-trip preservation + +sqlalchemy>=2.0.23 # ORM Toolkit featuring: + # - AsyncIO engine support + # - Hybrid query attributes + # - PostgreSQL JSONB extensions + +### APPLICATION FRAMEWORKS ### +click>=8.1.7 # CLI Framework with: + # - ANSI color support + # - Shell completion + # - Plugin architecture + +django>=4.2.11,<5.0 # Web Framework (LTS): + # - ASGI/WSGI dual-mode + # - Advanced migration engine + # - GIS spatial support + +### SECURITY ESSENTIALS ### +python-dotenv>=1.0.0 # Secrets management: + # - Vault integration hooks + # - Env var validation + +### PERFORMANCE ENHANCEMENTS ### +orjson>=3.9.10 # JSON processor: + # - SIMD acceleration + # - 4x faster than stdlib diff --git a/scripts/monitoring_alerts.sh b/scripts/monitoring_alerts.sh index d052a3cd..4c169cfc 100755 --- a/scripts/monitoring_alerts.sh +++ b/scripts/monitoring_alerts.sh @@ -3,4 +3,4 @@ aws cloudwatch put-metric-alarm \ --metric-name "CPUUtilization" \ --namespace "GitHubRunners" \ --threshold 80 \ - --comparison-operator GreaterThanThreshold \ No newline at end of file + --comparison-operator GreaterThanThreshold diff --git a/scripts/scripts/setup_environment.sh b/scripts/scripts/setup_environment.sh index 1a8cd33c..6c564e22 100755 --- a/scripts/scripts/setup_environment.sh +++ b/scripts/scripts/setup_environment.sh @@ -46,4 +46,4 @@ else fi # 5. Mensaje final -echo "🎉 Configuración del entorno completada. ¡Listo para empezar!" \ No newline at end of file +echo "🎉 Configuración del entorno completada. ¡Listo para empezar!" diff --git "a/t --version\023alir" "b/t --version\023alir" new file mode 100644 index 00000000..56337367 --- /dev/null +++ "b/t --version\023alir" @@ -0,0 +1,220 @@ +diff --git a/.gitignore b/.gitignore +index 5fbfa0d..3259bc8 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -6,3 +6,9 @@ __pycache__/ + *.egg-info/ + venv/ + *.log ++ ++# Archivos temporales ++filtered_reqs.txt ++ ++# Entornos virtual ++.venv/ +diff --git a/ai_core/dependency_analyzer.py b/ai_core/dependency_analyzer.py +index aa6021c..5a5179e 100755 +--- a/ai_core/dependency_analyzer.py ++++ b/ai_core/dependency_analyzer.py +@@ -1,23 +1,42 @@ + """ + Módulo principal de análisis de dependencias - Versión Termux + """ ++from pathlib import Path ++import subprocess +  + class DependencyAnalyzer: + def __init__(self): + self.dependencies = [] +-  ++  + def analyze(self, path): + """Método principal de análisis""" + print(f"Analizando dependencias en: {path}") +- # Implementación real aquí + return {"status": "success"} +  ++ def _get_package_dependencies(self, package_name): ++ """Obtiene dependencias de un paquete""" ++ try: ++ result = subprocess.run(['pip', 'show', package_name], ++ capture_output=True, text=True) ++ return result.stdout ++ except Exception as e: ++ return f"Error obteniendo dependencias: {str(e)}" ++ ++ def parse_requirements_txt(self, file_path): ++ """Parsea archivos requirements.txt""" ++ try: ++ with open(file_path) as f: ++ return [line.strip() for line in f if line.strip() and not line.startswith('#')] ++ except Exception as e: ++ print(f"Error leyendo archivo: {str(e)}") ++ return [] ++ + def main(): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True) + args = parser.parse_args() +-  ++ + analyzer = DependencyAnalyzer() + result = analyzer.analyze(args.path) + print(result) +diff --git a/filtered_reqs.txt b/filtered_reqs.txt +new file mode 100644 +index 0000000..386fff3 +--- /dev/null ++++ b/filtered_reqs.txt +@@ -0,0 +1,39 @@ ++# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements ++# Last updated: 2024-06-20 ++ ++# HTTP Requests ++requests>=2.25.1 # Modern HTTP client with SSL verification ++ ++# Configuration ++pyyaml>=6.0 # YAML parser for configuration files ++ ++# Database ORM ++sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support ++ ++# CLI Framework ++click>=8.0.0 # Command Line Interface creation toolkit ++ ++# Web Framework ++django>=4.0,<5.0 # Full-stack web framework (LTS version) ++ ++# Local Package (install in editable mode) ++-e . # Installs your myproject package from current directory# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements ++# Last updated: 2024-04-12 ++ ++# HTTP Requests ++requests>=2.25.1 # Modern HTTP client with SSL verification ++ ++# Configuration ++pyyaml>=6.0 # YAML parser for configuration files ++ ++# Database ORM ++sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support ++ ++# CLI Framework ++click>=8.0.0 # Command Line Interface creation toolkit ++ ++# Web Framework ++django>=4.0,<5.0 # Full-stack web framework (LTS version) ++ ++# Local Package (install in editable mode) ++-e . # Installs your myproject package from current directory +diff --git a/tests/mocks.py b/tests/mocks.py +new file mode 100644 +index 0000000..642293b +--- /dev/null ++++ b/tests/mocks.py +@@ -0,0 +1,7 @@ ++from unittest.mock import MagicMock ++from ai_core.dependency_analyzer import DependencyAnalyzer ++ ++def get_mock_analyzer(): ++ analyzer = DependencyAnalyzer() ++ analyzer._get_package_dependencies = MagicMock(return_value="mock_data") ++ return analyzer +diff --git a/tests/unit_tests/test_data/requirements_sample.txt b/tests/unit_tests/test_data/requirements_sample.txt +new file mode 100644 +index 0000000..bd53dbb +--- /dev/null ++++ b/tests/unit_tests/test_data/requirements_sample.txt +@@ -0,0 +1,4 @@ ++numpy>=1.19.0,<2.0.0 ++pandas==1.3.0 ++# Comentario ++ +diff --git a/tests/unit_tests/test_dependency_analyzer.py b/tests/unit_tests/test_dependency_analyzer.py +index 5a5575c..cc39d91 100755 +--- a/tests/unit_tests/test_dependency_analyzer.py ++++ b/tests/unit_tests/test_dependency_analyzer.py +@@ -7,37 +7,49 @@ class TestDependencyAnalyzer(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.test_data_dir = Path(__file__).parent / "test_data" ++ cls.test_data_dir.mkdir(exist_ok=True) +  ++ # Crear archivo de prueba con dependencias complejas ++ cls.test_file = cls.test_data_dir / "requirements_sample.txt" ++ with open(cls.test_file, "w") as f: ++ f.write("numpy>=1.19.0,<2.0.0\npandas==1.3.0\n# Comentario\n\n") ++ ++ def setUp(self): ++ self.analyzer = DependencyAnalyzer() ++ + def test_parse_requirements_txt(self): +- """Test parsing standard requirements.txt""" +- analyzer = DependencyAnalyzer() +- test_file = self.test_data_dir / "requirements_sample.txt" +-  +- with patch.object(analyzer, '_get_package_dependencies') as mock_deps: +- mock_deps.return_value = {} +- analyzer._parse_dependencies(test_file) +-  +- self.assertIn("numpy", analyzer.dependency_graph) +- self.assertEqual( +- analyzer.dependency_graph["numpy"]["version"],  +- ">=1.19.0,<2.0.0" +- ) +-  ++ """Test para parse_requirements_txt con versiones""" ++ result = self.analyzer.parse_requirements_txt(self.test_file) ++ self.assertEqual(len(result), 2) ++ self.assertIn("numpy>=1.19.0,<2.0.0", result) ++ self.assertIn("pandas==1.3.0", result) ++ self.assertNotIn("# Comentario", result) ++ + @patch('subprocess.run') +- def test_package_dependencies(self, mock_run): +- """Test getting package dependencies""" ++ def test_get_package_dependencies(self, mock_run): ++ """Test mockeado para _get_package_dependencies""" ++ # Configurar mock + mock_result = MagicMock() + mock_result.returncode = 0 +- mock_result.stdout = "Requires: pandas, scipy\nVersion: 1.2.3" ++ mock_result.stdout = "Name: numpy\nVersion: 1.21.0\nRequires: pandas, scipy" + mock_run.return_value = mock_result ++ ++ # Ejecutar prueba ++ result = self.analyzer._get_package_dependencies("numpy") +  +- analyzer = DependencyAnalyzer() +- deps = analyzer._get_package_dependencies("numpy") +-  +- self.assertIn("pandas", deps) +- self.assertIn("scipy", deps) +-  +- # [...] Additional test cases ++ # Verificar resultados ++ self.assertIn("Name: numpy", result) ++ self.assertIn("Version: 1.21.0", result) ++ self.assertIn("Requires: pandas, scipy", result) ++ mock_run.assert_called_once_with(['pip', 'show', 'numpy'],  ++ capture_output=True,  ++ text=True) ++ ++ def test_analyze_method(self): ++ """Test para el método analyze""" ++ test_path = "/fake/path/to/project" ++ result = self.analyzer.analyze(test_path) ++ self.assertEqual(result["status"], "success") +  + if __name__ == '__main__': +- unittest.main() +\ No newline at end of file ++ unittest.main() +diff --git a/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt b/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt +new file mode 100644 +index 0000000..5da331c +--- /dev/null ++++ b/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt +@@ -0,0 +1,2 @@ ++numpy ++pandas diff --git a/tall pytest pytest-cov b/tall pytest pytest-cov new file mode 100644 index 00000000..56337367 --- /dev/null +++ b/tall pytest pytest-cov @@ -0,0 +1,220 @@ +diff --git a/.gitignore b/.gitignore +index 5fbfa0d..3259bc8 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -6,3 +6,9 @@ __pycache__/ + *.egg-info/ + venv/ + *.log ++ ++# Archivos temporales ++filtered_reqs.txt ++ ++# Entornos virtual ++.venv/ +diff --git a/ai_core/dependency_analyzer.py b/ai_core/dependency_analyzer.py +index aa6021c..5a5179e 100755 +--- a/ai_core/dependency_analyzer.py ++++ b/ai_core/dependency_analyzer.py +@@ -1,23 +1,42 @@ + """ + Módulo principal de análisis de dependencias - Versión Termux + """ ++from pathlib import Path ++import subprocess +  + class DependencyAnalyzer: + def __init__(self): + self.dependencies = [] +-  ++  + def analyze(self, path): + """Método principal de análisis""" + print(f"Analizando dependencias en: {path}") +- # Implementación real aquí + return {"status": "success"} +  ++ def _get_package_dependencies(self, package_name): ++ """Obtiene dependencias de un paquete""" ++ try: ++ result = subprocess.run(['pip', 'show', package_name], ++ capture_output=True, text=True) ++ return result.stdout ++ except Exception as e: ++ return f"Error obteniendo dependencias: {str(e)}" ++ ++ def parse_requirements_txt(self, file_path): ++ """Parsea archivos requirements.txt""" ++ try: ++ with open(file_path) as f: ++ return [line.strip() for line in f if line.strip() and not line.startswith('#')] ++ except Exception as e: ++ print(f"Error leyendo archivo: {str(e)}") ++ return [] ++ + def main(): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True) + args = parser.parse_args() +-  ++ + analyzer = DependencyAnalyzer() + result = analyzer.analyze(args.path) + print(result) +diff --git a/filtered_reqs.txt b/filtered_reqs.txt +new file mode 100644 +index 0000000..386fff3 +--- /dev/null ++++ b/filtered_reqs.txt +@@ -0,0 +1,39 @@ ++# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements ++# Last updated: 2024-06-20 ++ ++# HTTP Requests ++requests>=2.25.1 # Modern HTTP client with SSL verification ++ ++# Configuration ++pyyaml>=6.0 # YAML parser for configuration files ++ ++# Database ORM ++sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support ++ ++# CLI Framework ++click>=8.0.0 # Command Line Interface creation toolkit ++ ++# Web Framework ++django>=4.0,<5.0 # Full-stack web framework (LTS version) ++ ++# Local Package (install in editable mode) ++-e . # Installs your myproject package from current directory# MechanicalMind-Dependency-AI v3.0.1 - Core Requirements ++# Last updated: 2024-04-12 ++ ++# HTTP Requests ++requests>=2.25.1 # Modern HTTP client with SSL verification ++ ++# Configuration ++pyyaml>=6.0 # YAML parser for configuration files ++ ++# Database ORM ++sqlalchemy>=1.4.0 # SQL toolkit and ORM with async support ++ ++# CLI Framework ++click>=8.0.0 # Command Line Interface creation toolkit ++ ++# Web Framework ++django>=4.0,<5.0 # Full-stack web framework (LTS version) ++ ++# Local Package (install in editable mode) ++-e . # Installs your myproject package from current directory +diff --git a/tests/mocks.py b/tests/mocks.py +new file mode 100644 +index 0000000..642293b +--- /dev/null ++++ b/tests/mocks.py +@@ -0,0 +1,7 @@ ++from unittest.mock import MagicMock ++from ai_core.dependency_analyzer import DependencyAnalyzer ++ ++def get_mock_analyzer(): ++ analyzer = DependencyAnalyzer() ++ analyzer._get_package_dependencies = MagicMock(return_value="mock_data") ++ return analyzer +diff --git a/tests/unit_tests/test_data/requirements_sample.txt b/tests/unit_tests/test_data/requirements_sample.txt +new file mode 100644 +index 0000000..bd53dbb +--- /dev/null ++++ b/tests/unit_tests/test_data/requirements_sample.txt +@@ -0,0 +1,4 @@ ++numpy>=1.19.0,<2.0.0 ++pandas==1.3.0 ++# Comentario ++ +diff --git a/tests/unit_tests/test_dependency_analyzer.py b/tests/unit_tests/test_dependency_analyzer.py +index 5a5575c..cc39d91 100755 +--- a/tests/unit_tests/test_dependency_analyzer.py ++++ b/tests/unit_tests/test_dependency_analyzer.py +@@ -7,37 +7,49 @@ class TestDependencyAnalyzer(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.test_data_dir = Path(__file__).parent / "test_data" ++ cls.test_data_dir.mkdir(exist_ok=True) +  ++ # Crear archivo de prueba con dependencias complejas ++ cls.test_file = cls.test_data_dir / "requirements_sample.txt" ++ with open(cls.test_file, "w") as f: ++ f.write("numpy>=1.19.0,<2.0.0\npandas==1.3.0\n# Comentario\n\n") ++ ++ def setUp(self): ++ self.analyzer = DependencyAnalyzer() ++ + def test_parse_requirements_txt(self): +- """Test parsing standard requirements.txt""" +- analyzer = DependencyAnalyzer() +- test_file = self.test_data_dir / "requirements_sample.txt" +-  +- with patch.object(analyzer, '_get_package_dependencies') as mock_deps: +- mock_deps.return_value = {} +- analyzer._parse_dependencies(test_file) +-  +- self.assertIn("numpy", analyzer.dependency_graph) +- self.assertEqual( +- analyzer.dependency_graph["numpy"]["version"],  +- ">=1.19.0,<2.0.0" +- ) +-  ++ """Test para parse_requirements_txt con versiones""" ++ result = self.analyzer.parse_requirements_txt(self.test_file) ++ self.assertEqual(len(result), 2) ++ self.assertIn("numpy>=1.19.0,<2.0.0", result) ++ self.assertIn("pandas==1.3.0", result) ++ self.assertNotIn("# Comentario", result) ++ + @patch('subprocess.run') +- def test_package_dependencies(self, mock_run): +- """Test getting package dependencies""" ++ def test_get_package_dependencies(self, mock_run): ++ """Test mockeado para _get_package_dependencies""" ++ # Configurar mock + mock_result = MagicMock() + mock_result.returncode = 0 +- mock_result.stdout = "Requires: pandas, scipy\nVersion: 1.2.3" ++ mock_result.stdout = "Name: numpy\nVersion: 1.21.0\nRequires: pandas, scipy" + mock_run.return_value = mock_result ++ ++ # Ejecutar prueba ++ result = self.analyzer._get_package_dependencies("numpy") +  +- analyzer = DependencyAnalyzer() +- deps = analyzer._get_package_dependencies("numpy") +-  +- self.assertIn("pandas", deps) +- self.assertIn("scipy", deps) +-  +- # [...] Additional test cases ++ # Verificar resultados ++ self.assertIn("Name: numpy", result) ++ self.assertIn("Version: 1.21.0", result) ++ self.assertIn("Requires: pandas, scipy", result) ++ mock_run.assert_called_once_with(['pip', 'show', 'numpy'],  ++ capture_output=True,  ++ text=True) ++ ++ def test_analyze_method(self): ++ """Test para el método analyze""" ++ test_path = "/fake/path/to/project" ++ result = self.analyzer.analyze(test_path) ++ self.assertEqual(result["status"], "success") +  + if __name__ == '__main__': +- unittest.main() +\ No newline at end of file ++ unittest.main() +diff --git a/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt b/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt +new file mode 100644 +index 0000000..5da331c +--- /dev/null ++++ b/tests/unit_tests/tests/unit_tests/test_data/requirements_sample.txt +@@ -0,0 +1,2 @@ ++numpy ++pandas diff --git a/tests/mocks.py b/tests/mocks.py index 642293b0..d552b93b 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -1,6 +1,7 @@ from unittest.mock import MagicMock from ai_core.dependency_analyzer import DependencyAnalyzer + def get_mock_analyzer(): analyzer = DependencyAnalyzer() analyzer._get_package_dependencies = MagicMock(return_value="mock_data") diff --git a/tests/unit_tests/test_compatibility_matrix.py b/tests/unit_tests/test_compatibility_matrix.py new file mode 100644 index 00000000..75632f5c --- /dev/null +++ b/tests/unit_tests/test_compatibility_matrix.py @@ -0,0 +1,47 @@ +import os +import unittest +from ai_core.compatibility_matrix import CompatibilityMatrix +from tempfile import NamedTemporaryFile + + +class TestCompatibilityMatrix(unittest.TestCase): + def setUp(self): + self.temp_db = NamedTemporaryFile(delete=False) + self.db_path = self.temp_db.name + self.cm = CompatibilityMatrix(self.db_path) + + def tearDown(self): + self.temp_db.close() + os.unlink(self.db_path) + + def test_add_record_success(self): + """Test successful addition of compatibility record""" + result = self.cm.add_compatibility_record( + "numpy", "1.21.0", "3.10", "compatible" + ) + self.assertTrue(result) + + def test_add_record_failure_empty(self): + """Test handling of empty data""" + result = self.cm.add_compatibility_record("", "", "", "") + self.assertFalse(result) + + def test_add_record_failure_none(self): + """Test handling of None values""" + result = self.cm.add_compatibility_record(None, "1.21.0", "3.10", "compatible") + self.assertFalse(result) + + def test_check_compatibility(self): + """Test compatibility checking""" + self.cm.add_compatibility_record("pandas", "1.3.0", "3.9", "compatible") + status = self.cm.check_compatibility("pandas", "1.3.0", "3.9") + self.assertEqual(status, "compatible") + + def test_check_unknown_compatibility(self): + """Test checking unknown package""" + status = self.cm.check_compatibility("unknown", "1.0", "3.10") + self.assertEqual(status, "unknown") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit_tests/test_data/invalid_knowledge.json b/tests/unit_tests/test_data/invalid_knowledge.json new file mode 100644 index 00000000..a4aa4ce3 --- /dev/null +++ b/tests/unit_tests/test_data/invalid_knowledge.json @@ -0,0 +1 @@ +{"key": "value" diff --git a/tests/unit_tests/test_data/requirements_sample.txt b/tests/unit_tests/test_data/requirements_sample.txt index bd53dbb2..c089311e 100644 --- a/tests/unit_tests/test_data/requirements_sample.txt +++ b/tests/unit_tests/test_data/requirements_sample.txt @@ -1,4 +1,3 @@ numpy>=1.19.0,<2.0.0 pandas==1.3.0 # Comentario - diff --git a/tests/unit_tests/test_data/valid_knowledge.json b/tests/unit_tests/test_data/valid_knowledge.json new file mode 100644 index 00000000..d3d6406a --- /dev/null +++ b/tests/unit_tests/test_data/valid_knowledge.json @@ -0,0 +1,14 @@ +{ + "common_errors": { + "TEST_ERROR": { + "description": "Test error", + "patterns": [ + "test pattern" + ], + "solutions": [ + "test solution" + ], + "severity": "low" + } + } +} diff --git a/tests/unit_tests/test_dependency_analyzer.py b/tests/unit_tests/test_dependency_analyzer.py index cc39d915..292cfa21 100755 --- a/tests/unit_tests/test_dependency_analyzer.py +++ b/tests/unit_tests/test_dependency_analyzer.py @@ -3,12 +3,13 @@ from unittest.mock import patch, MagicMock from ai_core.dependency_analyzer import DependencyAnalyzer + class TestDependencyAnalyzer(unittest.TestCase): @classmethod def setUpClass(cls): cls.test_data_dir = Path(__file__).parent / "test_data" cls.test_data_dir.mkdir(exist_ok=True) - + # Crear archivo de prueba con dependencias complejas cls.test_file = cls.test_data_dir / "requirements_sample.txt" with open(cls.test_file, "w") as f: @@ -25,7 +26,7 @@ def test_parse_requirements_txt(self): self.assertIn("pandas==1.3.0", result) self.assertNotIn("# Comentario", result) - @patch('subprocess.run') + @patch("subprocess.run") def test_get_package_dependencies(self, mock_run): """Test mockeado para _get_package_dependencies""" # Configurar mock @@ -36,14 +37,14 @@ def test_get_package_dependencies(self, mock_run): # Ejecutar prueba result = self.analyzer._get_package_dependencies("numpy") - + # Verificar resultados self.assertIn("Name: numpy", result) self.assertIn("Version: 1.21.0", result) self.assertIn("Requires: pandas, scipy", result) - mock_run.assert_called_once_with(['pip', 'show', 'numpy'], - capture_output=True, - text=True) + mock_run.assert_called_once_with( + ["pip", "show", "numpy"], capture_output=True, text=True + ) def test_analyze_method(self): """Test para el método analyze""" @@ -51,5 +52,6 @@ def test_analyze_method(self): result = self.analyzer.analyze(test_path) self.assertEqual(result["status"], "success") -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/unit_tests/test_knowledge_base.py b/tests/unit_tests/test_knowledge_base.py new file mode 100644 index 00000000..5359d62f --- /dev/null +++ b/tests/unit_tests/test_knowledge_base.py @@ -0,0 +1,71 @@ +""" +MechanicalMind Knowledge Base Test Suite - Versión Mejorada y Corregida +====================================================================== +""" + +import json +import time +from pathlib import Path +from typing import Dict, Any +import unittest +from unittest.mock import patch, mock_open +from ai_core.knowledge_base import load_error_knowledge + + +class TestKnowledgeBase(unittest.TestCase): + """Caso de prueba para el módulo de base de conocimiento""" + + @classmethod + def setUpClass(cls) -> None: + """Configuración inicial de datos de prueba""" + cls.test_data_dir = Path(__file__).parent / "test_data" + cls.test_data_dir.mkdir(exist_ok=True) + + # Archivo válido + cls.valid_kb_path = cls.test_data_dir / "valid_knowledge.json" + cls.valid_data: Dict[str, Any] = { + "common_errors": { + "TEST_ERROR": { + "description": "Test error", + "patterns": ["test pattern"], + "solutions": ["test solution"], + "severity": "low", + } + } + } + with open(cls.valid_kb_path, "w", encoding="utf-8") as f: + json.dump(cls.valid_data, f, indent=2) + + # Archivo inválido + cls.invalid_kb_path = cls.test_data_dir / "invalid_knowledge.json" + with open(cls.invalid_kb_path, "w", encoding="utf-8") as f: + f.write('{"key": "value"') # JSON incompleto + + def test_large_file_performance(self): + """Test de carga con archivo grande""" + large_data = {"common_errors": {f"ERR{i}": {} for i in range(1000)}} + with patch("builtins.open", mock_open(read_data=json.dumps(large_data))): + start = time.time() + result = load_error_knowledge() + elapsed = time.time() - start + self.assertLess(elapsed, 0.5) # Menos de 500ms + + def test_load_valid_knowledge(self) -> None: + """Test Case KB-101: Carga exitosa de conocimiento válido""" + result = load_error_knowledge(str(self.valid_kb_path)) + self.assertEqual(result, self.valid_data) + self.assertIn("TEST_ERROR", result["common_errors"]) + + def test_load_invalid_json(self) -> None: + """Test Case KB-401: Manejo de JSON inválido""" + with self.assertRaises(ImportError): + load_error_knowledge(str(self.invalid_kb_path)) + + def test_file_not_found(self) -> None: + """Test Case KB-201: Manejo de archivo no encontrado""" + with self.assertRaises(ImportError): + load_error_knowledge("non_existent_file.json") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/unit_tests/test_utils_init.py b/tests/unit_tests/test_utils_init.py new file mode 100644 index 00000000..e0319621 --- /dev/null +++ b/tests/unit_tests/test_utils_init.py @@ -0,0 +1,18 @@ +import pytest +from ai_core.utils import __version__, __author__ + + +def test_version(): + assert __version__ == "3.0.1" + + +def test_author(): + assert "MechMind Team" in __author__ + assert "dev@mechmind.example" in __author__ + + +def test_imports(): + from ai_core.utils import configure_logging, make_http_request + + assert callable(configure_logging) + assert callable(make_http_request)