195 lines
6.9 KiB
Python
195 lines
6.9 KiB
Python
#!/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())
|