from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from typing import Any @dataclass(frozen=True) class ArtifactSpec: include: list[str] exclude: list[str] max_files_per_iteration: int @dataclass(frozen=True) class MutationSpec: mode: str allowed_file_types: list[str] max_changed_lines: int @dataclass(frozen=True) class RunnerSpec: command: str cwd: str timeout_seconds: int @dataclass(frozen=True) class ScorerParseSpec: format: str score_field: str metrics_field: str @dataclass(frozen=True) class ScorerSpec: type: str command: str parse: ScorerParseSpec @dataclass(frozen=True) class ObjectiveSpec: primary_metric: str direction: str @dataclass(frozen=True) class ConstraintSpec: metric: str op: str value: Any @dataclass(frozen=True) class PolicySpec: keep_if: str tie_breakers: list[dict[str, str]] on_failure: str @dataclass(frozen=True) class BudgetSpec: max_iterations: int max_failures: int @dataclass(frozen=True) class LoggingSpec: results_file: str candidate_dir: str @dataclass(frozen=True) class TaskSpec: id: str description: str artifacts: ArtifactSpec mutation: MutationSpec runner: RunnerSpec scorer: ScorerSpec objective: ObjectiveSpec constraints: list[ConstraintSpec] policy: PolicySpec budget: BudgetSpec logging: LoggingSpec root_dir: Path @dataclass(frozen=True) class BaselineSnapshot: file_contents: dict[Path, str] file_hashes: dict[Path, str] @dataclass(frozen=True) class RunResult: command: str cwd: Path exit_code: int runtime_seconds: float stdout: str stderr: str @dataclass(frozen=True) class ScoreResult: primary_score: float metrics: dict[str, Any] raw_output: dict[str, Any] @dataclass(frozen=True) class DecisionResult: status: str reason: str baseline_score: float | None candidate_score: float | None constraint_failures: list[str] = field(default_factory=list)