""" Helper utilities for the ChipForge server environment. """ import json import subprocess from pathlib import Path from typing import Any, Dict, List from .constants import TASKS_DIR, TOOL_TIMEOUT def discover_tasks() -> List[Path]: """Return sorted list of task directories under TASKS_DIR (recursive).""" if not TASKS_DIR.is_dir(): return [] return sorted( p.parent for p in TASKS_DIR.rglob("task.json") if p.is_file() and p.parent.is_dir() ) def categorize_tasks(tasks: List[Path]) -> Dict[str, List[Path]]: """Organize tasks into difficulty categories.""" categorized_tasks = {"easy": [], "medium": [], "hard": []} for task in tasks: try: with open(task / "task.json", "r") as f: meta = json.load(f) diff = meta.get("difficulty", "medium").lower() if diff in categorized_tasks: categorized_tasks[diff].append(task) else: categorized_tasks["medium"].append(task) except Exception: categorized_tasks["medium"].append(task) return categorized_tasks def run_tool(cmd: List[str], cwd: str, timeout: int = TOOL_TIMEOUT) -> Dict[str, Any]: """Run a shell command and return stdout, stderr, returncode.""" try: result = subprocess.run( cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout, ) return { "stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode, } except subprocess.TimeoutExpired: return { "stdout": "", "stderr": f"Command timed out after {timeout}s", "returncode": -1, } except FileNotFoundError as e: return { "stdout": "", "stderr": f"Tool not found: {e}", "returncode": -1, } def extract_error_summary(stderr: str, stdout: str = "") -> str: """Extract a one-line error summary from tool output.""" combined = stderr + "\n" + stdout for line in combined.splitlines(): line = line.strip() if not line: continue lower = line.lower() if any(kw in lower for kw in ("error", "syntax", "warning", "latch", "fail")): return line[:200] for line in stderr.splitlines(): line = line.strip() if line: return line[:200] return ""