Files
wehub-resource-sync 327604cc89
Rebuild Cookbook Website / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:41:49 +08:00

146 lines
5.3 KiB
Python

from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
EXAMPLE_ROOT = Path(__file__).resolve().parent
EXPECTED_PATCH_MARKERS = {
"support_reply_service": [
"customer_support_bot/client.py",
"customer_support_bot/replies.py",
],
"case_summary_service": [
"case_summary_service/client.py",
"case_summary_service/summaries.py",
],
}
def read_json(path: Path) -> dict[str, Any]:
with path.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{path} must contain a JSON object.")
return payload
def read_jsonl(path: Path) -> list[dict[str, Any]]:
events: list[dict[str, Any]] = []
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
if not line.strip():
continue
payload = json.loads(line)
if not isinstance(payload, dict):
raise ValueError(f"{path}:{line_number} must contain a JSON object.")
events.append(payload)
return events
def require_output(path: Path) -> None:
if not path.exists():
raise FileNotFoundError(
f"Missing {path}. Run the full migration agent before running artifact evals."
)
if path.stat().st_size == 0:
raise ValueError(f"{path} is empty.")
def require_contains(text: str, marker: str, *, artifact: str) -> None:
if marker not in text:
raise ValueError(f"Expected {artifact} to contain {marker!r}.")
def require_result_value(result: dict[str, Any], field: str, marker: str) -> None:
value = result.get(field)
if not isinstance(value, str) or marker not in value:
raise ValueError(f"Expected result[{field!r}] to contain {marker!r}.")
def validate_migration_artifacts(output_dir: Path, *, task_name: str | None = None) -> None:
report_path = output_dir / "migration_report.md"
patch_path = output_dir / "migration.patch"
result_path = output_dir / "migration_result.json"
audit_path = output_dir / "migration_audit.jsonl"
for path in [report_path, patch_path, result_path, audit_path]:
require_output(path)
result = read_json(result_path)
patch = patch_path.read_text(encoding="utf-8")
report = report_path.read_text(encoding="utf-8")
audit_events = read_jsonl(audit_path)
require_result_value(result, "baseline_test_command", "unittest")
require_result_value(result, "check_command", "compileall")
require_result_value(result, "final_test_command", "unittest")
final_test_result = str(result.get("final_test_result", "")).lower()
if "pass" not in final_test_result and "ok" not in final_test_result:
raise ValueError("Expected final_test_result to describe a passing test run.")
changed_files = result.get("changed_files")
if not isinstance(changed_files, list) or not changed_files:
raise ValueError("Expected result['changed_files'] to be a non-empty list.")
require_contains(patch, "responses.create", artifact=str(patch_path))
require_contains(patch, "output_text", artifact=str(patch_path))
for marker in EXPECTED_PATCH_MARKERS.get(task_name or "", []):
require_contains(patch, marker, artifact=str(patch_path))
require_contains(patch, "tests/", artifact=str(patch_path))
require_contains(report.lower(), "responses", artifact=str(report_path))
require_contains(report.lower(), "test", artifact=str(report_path))
if not any(event.get("event") == "host_artifacts_written" for event in audit_events):
raise ValueError("Expected audit log to include a host_artifacts_written event.")
def validate_output_root(output_dir: Path) -> None:
batch_summary_path = output_dir / "batch_summary.json"
if not batch_summary_path.exists():
validate_migration_artifacts(output_dir)
return
batch_summary = read_json(batch_summary_path)
task_summaries = batch_summary.get("task_summaries")
if not isinstance(task_summaries, list) or not task_summaries:
raise ValueError("Expected batch_summary.json to include task_summaries.")
for summary in task_summaries:
if not isinstance(summary, dict):
raise ValueError("Each task summary must be a JSON object.")
task_name = summary.get("task_name")
task_output_dir = summary.get("output_dir")
if not isinstance(task_name, str) or not isinstance(task_output_dir, str):
raise ValueError("Each task summary must include task_name and output_dir.")
validate_migration_artifacts(Path(task_output_dir), task_name=task_name)
def main() -> None:
parser = argparse.ArgumentParser(description="Validate generated migration-agent artifacts.")
parser.add_argument(
"--output-dir",
type=Path,
default=EXAMPLE_ROOT / "outputs",
help=(
"Directory containing batch_summary.json or a single task's "
"migration_report.md, migration.patch, migration_result.json, "
"and migration_audit.jsonl."
),
)
args = parser.parse_args()
try:
validate_output_root(args.output_dir)
except Exception as exc:
raise SystemExit(f"Artifact eval failed: {exc}") from exc
print(f"Migration artifact evals passed for {args.output_dir}")
if __name__ == "__main__":
main()