#!/usr/bin/env python3 from __future__ import annotations import argparse import datetime as dt import json from dataclasses import dataclass from pathlib import Path from typing import Any REPO_ROOT = Path(__file__).resolve().parents[1] @dataclass class CheckItem: name: str status: str details: str def read_json(path: Path) -> dict[str, Any] | list[Any] | None: try: return json.loads(path.read_text(encoding="utf-8")) except Exception: return None def check_file_exists(path: Path, name: str) -> CheckItem: if path.exists(): return CheckItem(name=name, status="PASS", details=str(path)) return CheckItem(name=name, status="FAIL", details=f"missing: {path}") def check_stress_baseline(path: Path) -> CheckItem: payload = read_json(path) if not isinstance(payload, dict): return CheckItem("baseline_stress_102", "FAIL", f"invalid_json: {path}") totals = payload.get("totals") if not isinstance(totals, dict): return CheckItem("baseline_stress_102", "FAIL", "totals_missing") total = int(totals.get("questions_total", -1)) strict = int(totals.get("strict_pass_count", -1)) route = int(totals.get("route_pass_count", -1)) if total == 102 and strict == 102 and route == 102: return CheckItem( "baseline_stress_102", "PASS", f"strict={strict}/{total}, route={route}/{total}", ) return CheckItem( "baseline_stress_102", "FAIL", f"expected 102/102, got strict={strict}/{total}, route={route}/{total}", ) def check_followup_baseline(path: Path) -> CheckItem: payload = read_json(path) if not isinstance(payload, dict): return CheckItem("baseline_followup_25", "FAIL", f"invalid_json: {path}") totals = payload.get("totals") if not isinstance(totals, dict): return CheckItem("baseline_followup_25", "FAIL", "totals_missing") total = int(totals.get("questions_total", -1)) strict = int(totals.get("strict_pass_count", -1)) route = int(totals.get("route_pass_count", -1)) if total == 25 and strict == 25 and route == 25: return CheckItem( "baseline_followup_25", "PASS", f"strict={strict}/{total}, route={route}/{total}", ) return CheckItem( "baseline_followup_25", "FAIL", f"expected 25/25, got strict={strict}/{total}, route={route}/{total}", ) def check_nightly(path: Path) -> CheckItem: payload = read_json(path) if not isinstance(payload, dict): return CheckItem("nightly_regression_green", "FAIL", f"invalid_json: {path}") overall_ok = bool(payload.get("overall_ok")) packs = payload.get("packs") if not isinstance(packs, list): return CheckItem("nightly_regression_green", "FAIL", "packs_missing") bad = [] for pack in packs: if not isinstance(pack, dict): continue name = str(pack.get("pack") or "unknown") if not bool(pack.get("runner_ok")) or not bool(pack.get("validator_ok")) or not bool(pack.get("comparator_ok")): bad.append(name) if overall_ok and not bad: return CheckItem("nightly_regression_green", "PASS", f"overall_ok=true, packs={len(packs)}") return CheckItem("nightly_regression_green", "FAIL", f"overall_ok={overall_ok}, failed_packs={bad}") def compute_ready(items: list[CheckItem]) -> bool: return all(item.status == "PASS" for item in items) def write_report(path: Path, items: list[CheckItem], ready: bool) -> None: now = dt.datetime.now().astimezone().isoformat(timespec="seconds") lines: list[str] = [] lines.append("# Wave-1 Batch-1 Readiness Report") lines.append("") lines.append(f"- Generated at: `{now}`") lines.append(f"- Decision: **{'READY_FOR_PHASE_A' if ready else 'NOT_READY'}**") lines.append("") lines.append("## Checks") for item in items: mark = "PASS" if item.status == "PASS" else "FAIL" lines.append(f"- `{item.name}`: **{mark}** — {item.details}") lines.append("") lines.append("## Next Action") if ready: lines.append("- Start/continue Phase A for Batch-1 (domain card + acceptance set + implementation backlog).") else: lines.append("- Fix failed items above before coding Batch-1 runtime intents.") path.parent.mkdir(parents=True, exist_ok=True) path.write_text("\n".join(lines) + "\n", encoding="utf-8") def main() -> int: parser = argparse.ArgumentParser(description="Check readiness for Step-4 Wave-1 Batch-1.") parser.add_argument( "--out", default=str( REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "wave1_batch1_readiness_report_2026-04-02.md" ), ) args = parser.parse_args() path_global = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "global_execution_checklist_v1.md" path_step0 = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "step0_closeout_2026-04-02.md" path_plan = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "domain_expansion_implementation_plan_v1.md" path_general = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "general_domain_questions_analysis_plan_v1_2026-04-02.md" path_probe = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "management_route_probe_report_g1_2026-04-02.md" path_complex = REPO_ROOT / "docs" / "ADDRESS" / "address_query" / "complex_questions_status_and_reuse_map_2026-04-02.md" path_stress = ( REPO_ROOT / "docs" / "ADDRESS" / "runs" / "2026-04-02_Address_Slang_Live_Stress_2026-04-02_12-57-27" / "run_summary.json" ) path_followup = ( REPO_ROOT / "docs" / "ADDRESS" / "runs" / "2026-04-02_Address_Followup_Context_Chains_2026-04-02_19-15-Run5" / "run_summary.json" ) path_nightly = ( REPO_ROOT / "docs" / "ADDRESS" / "runs" / "2026-04-02_Address_Nightly_Regression_2026-04-02_17-35-00" / "nightly_summary.json" ) checks = [ check_file_exists(path_global, "master_checklist_exists"), check_file_exists(path_step0, "step0_closeout_exists"), check_file_exists(path_plan, "step4_plan_exists"), check_file_exists(path_general, "general_domain_analysis_exists"), check_file_exists(path_probe, "group1_probe_report_exists"), check_file_exists(path_complex, "complex_status_map_exists"), check_stress_baseline(path_stress), check_followup_baseline(path_followup), check_nightly(path_nightly), ] ready = compute_ready(checks) out_path = Path(args.out) write_report(out_path, checks, ready) print(f"[ok] readiness_report={out_path}") print(f"[ok] decision={'READY_FOR_PHASE_A' if ready else 'NOT_READY'}") return 0 if __name__ == "__main__": raise SystemExit(main())