#!/usr/bin/env python3 """Prepare actionable repair tasks from declared workflow inputs and failures.""" import json from pathlib import Path from wbv2_autopilot import ROOT, read_json, write_json REQUIRED = { "positive": ["before", "after"], "negative": ["before", "after"], "undo_redo": ["before", "after", "undo", "redo"], "save_reopen": ["saved", "reopened"], "determinism": ["first", "second"], } def prepare(root=ROOT): root = Path(root) backlog = read_json(root / "tests/golden/WBV2/web-product-backlog.json", {}) records = read_json(root / "tests/golden/WBV2/autopilot/state.json", {}).get("jobs", {}) tasks = [] for job in backlog.get("tasks", []): identifier = job["id"] path = root / "my-tools/local-workflows" / (identifier + ".json") if not path.is_file(): path = root / "tests/golden/WBV2/autopilot/local-plans" / (identifier + ".json") contract = read_json(path, {}) result = contract.get("result", {}) record = records.get(identifier, {}) incomplete = result.get("contractKind") == "compilation_only" or not result.get("cases") failed = record.get("state") in {"blocked", "provider_blocked", "needs_reconcile"} or ( record.get("state") == "retry_pending" and bool(record.get("reason"))) if not incomplete and not failed: continue declared = sorted(set(result.get("dependencies", []))) fixtures = [p for p in declared if p.endswith(".blend") and (root / p).is_file()] specs = sorted({p for check in result.get("checks", []) for p in check.get("argv", []) if p.endswith(".spec.ts")}) specs = [p if p.startswith("web/") else "web/" + p for p in specs] category = "behavior_contract_missing" if incomplete else "runtime_failure" reason = record.get("reason") or category if not incomplete and "receipt" in reason: category = "receipt_producer_mismatch" if not incomplete and not specs: category = "receipt_producer_missing" tasks.append({ "job": identifier, "module": job.get("module"), "workflow": job.get("title"), "state": "behavior_pending" if incomplete else "repair_required", "category": category, "reason": reason, "fixtures": fixtures, "fixtureSelectionRequired": not bool(fixtures), "routeIds": result.get("routeIds", []), "sourceUnits": result.get("sourceUnits", []), "sourceReferences": result.get("sourceReferences", []), "candidateTests": [p for p in specs if (root / p).is_file()], "missingTests": [p for p in specs if not (root / p).is_file()], "requiredObservations": REQUIRED, "nextActions": [ "Inspect the declared native route and fixture data", "Implement assertions and independent replay for this workflow", "Publish receipts only after assertions; bind exact caseId, kind and unitIds", ], "runDirectory": record.get("runDirectory"), }) destination = root / "tests/golden/WBV2/autopilot/behavior-pending.json" document = {"schemaVersion": 2, "policy": "repair plan only; no contract or acceptance mutation", "tasks": tasks} if read_json(destination, {}) != document: write_json(destination, document) return document def main(): document = prepare() print(json.dumps({"tasks": len(document["tasks"]), "behaviorPending": sum(t["state"] == "behavior_pending" for t in document["tasks"]), "repairRequired": sum(t["state"] == "repair_required" for t in document["tasks"])})) if __name__ == "__main__": main()