You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.1 KiB
Python

"""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),
)