refactor: mejorar inteligencia multi-archivo y saneamiento global

This commit is contained in:
Inaki Fernandez
2026-04-25 11:57:48 +02:00
parent 310dd03ed9
commit 194f51599b
2 changed files with 71 additions and 40 deletions

View File

@@ -11,19 +11,45 @@ class RepositoryController:
base_branch = self.repository.get_branch(self.default_branch_name)
self.repository.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base_branch.commit.sha)
def apply_state_changes(self, target_file_path: str, new_document_state: str, metrics: dict) -> None:
def apply_multi_file_changes(self, updates: dict, metrics: dict) -> None:
"""
updates: dict { "path/to/file": "new_content" }
"""
if not updates:
return
timestamp_slug = datetime.now().strftime("%Y%m%d-%H%M")
branch_name = f"bot/agentic-curation-{timestamp_slug}"
self._create_feature_branch(branch_name)
file_meta = self.repository.get_contents(target_file_path, ref=self.default_branch_name)
commit_signature = f"chore(docs): automatización agéntica de curaduría [{timestamp_slug}]"
self.repository.update_file(path=target_file_path, message=commit_signature, content=new_document_state, sha=file_meta.sha, branch=branch_name)
for file_path, content in updates.items():
try:
file_meta = self.repository.get_contents(file_path, ref=self.default_branch_name)
commit_signature = f"chore(docs): curaduría agéntica en {file_path} [{timestamp_slug}]"
self.repository.update_file(
path=file_path,
message=commit_signature,
content=content,
sha=file_meta.sha,
branch=branch_name
)
except Exception as e:
print(f"Error actualizando {file_path}: {e}")
pr_narrative = (
"## 🤖 Ejecución del Agente Curador de Nubenetes\n\n"
"Este Pull Request ha sido ensamblado de manera autónoma.\n\n"
"### Resumen:\n"
f"- Nuevos Enlaces (Redes): {metrics.get('social_injections', 0)}\n"
f"- Nuevos Enlaces (Descubrimiento): {metrics.get('autonomous_injections', 0)}\n"
"- Purga de enlaces caídos y duplicados completada."
"Este Pull Request ha sido ensamblado de manera autónoma con **Gemini**.\n\n"
"### Resumen de Inyecciones:\n"
f"- **Vía Redes Sociales (@nubenetes):** {metrics.get('social_injections', 0)}\n"
f"- **Vía Descubrimiento Autónomo (Trending):** {metrics.get('autonomous_injections', 0)}\n\n"
"### Labores de Mantenimiento:\n"
"- Purga global de hipervínculos caídos (404/500).\n"
"- Eliminación de duplicados y optimización de Markdown en múltiples archivos."
)
self.repository.create_pull(
title=f"Automated Knowledge Curation: {datetime.now().strftime('%d %b %Y')}",
body=pr_narrative,
head=branch_name,
base=self.default_branch_name
)
self.repository.create_pull(title=f"Curation: {datetime.now().strftime('%d %b %Y')}", body=pr_narrative, head=branch_name, base=self.default_branch_name)

View File

@@ -1,6 +1,6 @@
import asyncio
from datetime import datetime, timedelta
from src.config import TARGET_REPO, MAIN_DOC_FILE, MADRID_TZ, GH_TOKEN
from src.config import TARGET_REPO, MADRID_TZ, GH_TOKEN, NUBENETES_CATEGORIES
from src.ingestion_twikit import SocialDataExtractor
from src.markdown_ast import MarkdownSanitizer
from src.agentic_curator import evaluate_extracted_assets
@@ -10,8 +10,6 @@ from src.gitops_manager import RepositoryController
async def master_orchestrator():
git_controller = RepositoryController(GH_TOKEN, TARGET_REPO)
# DINÁMICO: Obtener la fecha del último commit en la carpeta docs/
# Si no hay commits previos o hay error, usamos la fecha de seguridad 2024-10-05
try:
commits = git_controller.repository.get_commits(path="docs")
last_commit_date = commits[0].commit.committer.date.replace(tzinfo=MADRID_TZ)
@@ -19,49 +17,56 @@ async def master_orchestrator():
except:
time_horizon = datetime(2024, 10, 5, 18, 36, tzinfo=MADRID_TZ)
print(f"Buscando actualizaciones desde: {time_horizon}")
print(f">>> Iniciando curaduría desde: {time_horizon}")
# 1. Extracción de X (Twitter)
# 1. Obtención de datos
twitter_client = SocialDataExtractor()
raw_social_links = await twitter_client.fetch_links_since(time_horizon)
# 2. Descubrimiento proactivo en GitHub
autonomous_links = await discover_trending_assets()
# 3. Evaluación con IA (Gemini)
# 2. Evaluación con IA
curated_social_links = await evaluate_extracted_assets(raw_social_links)
total_new_assets = curated_social_links + autonomous_links
# 4. Procesamiento de Markdown
# 3. Preparar cambios
markdown_sanitizer = MarkdownSanitizer()
repo_file_data = git_controller.repository.get_contents(MAIN_DOC_FILE)
document_state = repo_file_data.decoded_content.decode("utf-8")
file_updates = {}
# 5. Limpieza de enlaces rotos y duplicados (Higiene)
purified_document_state = await markdown_sanitizer.sanitize_document(document_state)
# Identificar qué archivos necesitan ser procesados
categories_to_update = set([a["category"] for asset in total_new_assets])
# 6. Inyección de nuevos recursos
final_document_state = purified_document_state
for asset in total_new_assets:
final_document_state = markdown_sanitizer.inject_curated_link(
final_document_state,
asset["category"],
asset["title"],
asset["url"],
asset["description"]
)
# 7. Persistencia vía Pull Request si hay cambios
if final_document_state.strip() != document_state.strip():
# 4. Procesar inyecciones y saneamiento
for category in NUBENETES_CATEGORIES:
file_path = f"docs/{category}.md"
try:
repo_file = git_controller.repository.get_contents(file_path)
content = repo_file.decoded_content.decode("utf-8")
# Saneamiento (siempre lo hacemos para mantener la salud)
new_content = await markdown_sanitizer.sanitize_document(content)
# Inyección si hay activos para esta categoría
for asset in total_new_assets:
if asset["category"] == category:
new_content = markdown_sanitizer.inject_curated_link(
new_content, category, asset["title"], asset["url"], asset["description"]
)
if new_content.strip() != content.strip():
file_updates[file_path] = new_content
except:
continue
# 5. Aplicar cambios vía GitOps
if file_updates:
metrics = {
"social_injections": len(curated_social_links),
"autonomous_injections": len(autonomous_links)
}
git_controller.apply_state_changes(MAIN_DOC_FILE, final_document_state, metrics)
print(f"Cambios detectados. PR abierta con {len(total_new_assets)} nuevos recursos.")
git_controller.apply_multi_file_changes(file_updates, metrics)
print(f">>> Éxito: PR abierta con cambios en {len(file_updates)} archivos.")
else:
print("El repositorio ya está actualizado. Sin cambios pendientes.")
print(">>> Sin cambios necesarios.")
if __name__ == "__main__":
asyncio.run(master_orchestrator())