diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..6e260e64 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,34 @@ +# Nubenetes Intelligent Curation: Meta-Instructions & Learning Roadmap + +Este archivo contiene las instrucciones acumuladas y la visión de largo plazo para el mantenimiento autónomo de Nubenetes.com. Los agentes de IA deben consultar este documento en cada iteración para garantizar la continuidad del aprendizaje. + +## 🧠 Core Mandates (Mandatos Principales) + +1. **Preservación de la Información**: NUNCA elimines resúmenes, comentarios o estrellas (🌟) que acompañan a los enlaces. El bot solo debe actualizar la URL o reorganizar la posición del ítem, nunca borrar el contexto descriptivo. +2. **Aprendizaje Persistente**: Utiliza `src/memory/health_learning.json` para almacenar el conocimiento sobre dominios (bloqueos anti-bot, estrategias exitosas) y patrones de navegación. +3. **Resiliencia Total**: El workflow debe ser capaz de continuar incluso si hay errores individuales en validaciones de links o archivos. Prioriza generar un resultado (PR) aunque sea parcial. +4. **Consolidación de Repositorios**: Ante un fallo en un enlace profundo de GitHub/GitLab, intenta siempre validar la raíz del repositorio antes de darlo por muerto. Preferimos enlaces estables a raíces de repositorios que deep-links volátiles. + +## 🛠️ Evolución Estructural (Progressive Reorganization) + +* **Subsecciones Inteligentes**: El sistema debe detectar categorías superpobladas (>15 links) y proponer subdivisiones semánticas usando Gemini. +* **Integridad de Navegación**: Cada cambio en los archivos markdown debe reflejarse en: + * `mkdocs.yml` (Menú horizontal/lateral). + * `docs/index.md` (Tabla de contenidos principal). + * El TOC interno de cada markdown (si lo tiene). +* **Curación de Huérfanos**: Audita periódicamente la carpeta `docs/` para encontrar archivos no enlazados e intégralos en la navegación según su temática. + +## 🚀 Estrategias de Evasión de Bloqueos + +El bot debe rotar entre perfiles para evitar ser detectado: +1. **Desktop/Google**: Petición estándar de escritorio. +2. **Mobile/Twitter**: Petición móvil con Referer de Twitter (alta tasa de éxito). +3. **Playwright/LinkedIn**: Navegación real con JS habilitado. +4. **Firefox/Reddit**: Perfil alternativo de escritorio. + +## 📈 Diario de Aprendizaje (Historial de Mejoras) + +* **Mayo 2026**: Implementación inicial del motor autónomo con Playwright y Wayback Machine. +* **Mayo 2026**: Añadido sistema de Evasión Multidimensional (5 intentos, rotación de perfiles). +* **Mayo 2026**: Creación del `AgenticCurator` para auditoría de navegación y consolidación de repositorios. +* **Mayo 2026**: Generación de PRs con analíticas visuales (Mermaid) y Matriz de Salud. diff --git a/src/intelligent_health_checker.py b/src/intelligent_health_checker.py index 4c7cc919..62b608ce 100644 --- a/src/intelligent_health_checker.py +++ b/src/intelligent_health_checker.py @@ -57,7 +57,6 @@ class IntelligentLinkCleaner: async def _check_url_with_retries(self, url: str, max_retries=5) -> Tuple[str, bool, Optional[str], str]: domain = url.split("//")[-1].split("/")[0] domain_info = self.learning_data["domains"].get(domain, {}) - use_playwright_first = domain_info.get("requires_playwright", False) # Estrategias de Evasión (Perfiles) strategies = [ @@ -68,9 +67,12 @@ class IntelligentLinkCleaner: {"type": "playwright", "ua": "Mozilla/5.0 (Linux; Android 13; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36", "ref": "https://www.google.com/", "desc": "PW Mobile/Google"} ] - if use_playwright_first: - # Reordenar para priorizar Playwright - strategies = [s for s in strategies if s["type"] == "playwright"] + [s for s in strategies if s["type"] == "http"] + # Inteligencia: Si ya conocemos qué estrategia funciona para este dominio, reordenamos + best_strat_idx = domain_info.get("best_strategy_idx") + if best_strat_idx is not None and best_strat_idx < len(strategies): + # Mover la mejor estrategia al principio + best_strat = strategies.pop(best_strat_idx) + strategies.insert(0, best_strat) for attempt in range(min(max_retries, len(strategies))): strategy = strategies[attempt] @@ -80,11 +82,16 @@ class IntelligentLinkCleaner: is_alive, reason = await self._check_url_logic(url, strategy) if is_alive: - if domain not in self.learning_data["domains"]: self.learning_data["domains"][domain] = {"success_count": 0, "fail_count": 0} - self.learning_data["domains"][domain]["success_count"] += 1 + if domain not in self.learning_data["domains"]: self.learning_data["domains"][domain] = {} + # Guardamos el índice real de la estrategia que funcionó + # (si movimos la mejor al principio, hay que mapearla de nuevo) + original_idx = attempt if best_strat_idx is None else (best_strat_idx if attempt == 0 else (attempt if attempt < best_strat_idx else attempt)) + self.learning_data["domains"][domain]["best_strategy_idx"] = original_idx + self.learning_data["domains"][domain]["success_count"] = self.learning_data["domains"][domain].get("success_count", 0) + 1 return url, True, None, f"Alive ({strategy['desc']})" if reason in ["404", "soft_404", "redirect_to_home"]: + # REPO CONSOLIDATION if any(git_host in url for git_host in ["github.com", "gitlab.com", "bitbucket.org"]): parts = url.split("/") if len(parts) > 4: @@ -92,7 +99,6 @@ class IntelligentLinkCleaner: root_alive, _ = await self._check_url_logic(repo_root, strategies[0]) if root_alive: return url, False, f"REPO_ROOT:{repo_root}", f"Consolidated (Original: {reason})" - # Si es el último intento y sigue dando error duro (404), verificamos Wayback if attempt == max_retries - 1: archived = await self._check_wayback(url) if archived: return url, False, f"ARCHIVE:{archived}", f"Archived (Original: {reason})"