"""Repair mode: use Claude Code to fix a broken site adapter.""" from __future__ import annotations from pathlib import Path from loguru import logger from gimme_job.config import GlobalConfig from gimme_job.runtime.claude_cli import ClaudeCodeClient, DEFAULT_REPAIR_TOOLS class RepairMode: def __init__(self, global_config: GlobalConfig, claude_client: ClaudeCodeClient): self.cfg = global_config self.claude = claude_client def repair_site(self, site_id: str) -> bool: from gimme_job.db.engine import get_engine, get_session_factory from gimme_job.db.repo import SiteConfigRepo, SiteRunRepo from gimme_job.utils.paths import project_root, sites_dir from rich.console import Console console = Console() logger.info(f"[repair] Starting repair for {site_id}") engine = get_engine() factory = get_session_factory(engine) # Load recent failure artifacts with factory() as session: recent_runs = SiteRunRepo().get_recent_runs(session, site_id, limit=5) # Find last failure with artifacts last_run = None for run in recent_runs: if run.status in ("failed", "repair_needed"): last_run = run break error_summary = last_run.error_summary if last_run else "No recent failure details" failure_classification = last_run.failure_classification if last_run else None dom_path = last_run.dom_snapshot_path if last_run else None ss_path = last_run.screenshot_path if last_run else None trace_path = last_run.trace_path if last_run else None # Format recent run history run_history = "\n".join( f"- {r.started_at.strftime('%Y-%m-%d %H:%M')} | {r.status} | items={r.items_found}" for r in recent_runs[:5] ) or "No run history" manifest_path = sites_dir() / f"{site_id}.yaml" adapter_path = project_root() / "gimme_job" / "adapters" / f"{site_id}.py" if not manifest_path.exists(): logger.error(f"[repair] Manifest not found: {manifest_path}") return False # Build repair prompt prompt = self._build_prompt( site_id=site_id, error_summary=error_summary, failure_classification=failure_classification, run_history=run_history, dom_path=dom_path, ss_path=ss_path, trace_path=trace_path, manifest_path=manifest_path, adapter_path=adapter_path, ) console.print("[cyan]Calling Claude Code for repair...[/cyan]") result = self.claude.run_prompt( prompt=prompt, cwd=project_root(), allowed_tools=DEFAULT_REPAIR_TOOLS, ) if not result.success: logger.error(f"[repair] Claude Code failed: {result.error}") return False # Run smoke test console.print(f"[cyan]Running smoke test...[/cyan]") import subprocess test_result = subprocess.run( ["uv", "run", "pytest", f"tests/adapters/test_{site_id}.py", "-v", "--tb=short"], cwd=str(project_root()), capture_output=True, text=True, timeout=120, ) if test_result.returncode != 0: logger.error(f"[repair] Smoke test failed:\n{test_result.stdout[-500:]}") return False # Clear repair_needed flag with factory() as session: SiteConfigRepo().set_repair_needed(session, site_id, False) SiteConfigRepo().reset_failures(session, site_id) # Also update the YAML manifest try: from gimme_job.config import load_site_manifest manifest = load_site_manifest(site_id) manifest.repair_needed = False manifest.to_yaml(manifest_path) except Exception as e: logger.warning(f"[repair] Could not update manifest YAML: {e}") logger.info(f"[repair] {site_id} repaired successfully") return True def _build_prompt( self, site_id: str, error_summary: str, failure_classification: str | None, run_history: str, dom_path: str | None, ss_path: str | None, trace_path: str | None, manifest_path: Path, adapter_path: Path, ) -> str: from jinja2 import Environment, FileSystemLoader from gimme_job.utils.paths import project_root prompts_dir = project_root() / "gimme_job" / "prompts" env = Environment(loader=FileSystemLoader(str(prompts_dir))) template = env.get_template("repair_user.md.j2") return template.render( site_id=site_id, error_summary=error_summary, failure_classification=failure_classification, recent_run_history=run_history, last_run_dom_snapshot_path=dom_path, last_run_screenshot_path=ss_path, last_run_trace_path=trace_path, current_manifest_path=str(manifest_path), current_adapter_path=str(adapter_path), )