#!/usr/bin/env python3 """Run the full read-only preflight for a 1C patch workspace.""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any from check_1c_patch_source_freshness import check_workspace_sources from check_1c_patch_workspace_integrity import check_workspace from diff_1c_patch_workspace import build_diff from validate_1c_patch_workspace_semantics import validate_workspace def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) def safety_from_workspace(workspace: Path) -> dict[str, Any]: path = workspace / "safety.json" if not path.exists(): return { "schema": "onec_change_proposal_safety_check.v1", "passed": False, "findings": [{"severity": "error", "code": "missing_safety_json", "message": f"Missing {path}"}], "counts": {"errors": 1, "warnings": 0}, } return load_json(path) def status_from_checks(safety: dict[str, Any], integrity: dict[str, Any], freshness: dict[str, Any], semantic: dict[str, Any], diff: dict[str, Any]) -> str: if not safety.get("passed") or not integrity.get("passed") or not freshness.get("passed") or not semantic.get("passed") or not diff.get("passed"): return "blocked" modified = (diff.get("counts") or {}).get("modified", 0) if modified: return "ready_for_review" return "ready_for_editing" def collect_gate(name: str, data: dict[str, Any]) -> dict[str, Any]: return { "name": name, "schema": data.get("schema"), "passed": data.get("passed"), "counts": data.get("counts"), } def next_actions(status: str) -> list[str]: if status == "blocked": return [ "Inspect failed gates and recreate the workspace if source files changed.", "Do not generate, package, or apply patches until all gates pass.", ] if status == "ready_for_editing": return [ "Edit only files under working/.", "Run preflight again after edits; BSL/Form.xml semantic validation is included.", ] return [ "Review the workspace diff.", "Run external BSL/1C validation in a disposable base before packaging or applying.", ] def build_preflight(workspace: Path, *, max_patch_chars: int) -> dict[str, Any]: safety = safety_from_workspace(workspace) integrity = check_workspace(workspace) freshness = check_workspace_sources(workspace) semantic = validate_workspace(workspace) diff = build_diff(workspace, max_patch_chars=max_patch_chars) status = status_from_checks(safety, integrity, freshness, semantic, diff) return { "schema": "onec_patch_preflight.v1", "workspace": str(workspace), "status": status, "passed": status != "blocked", "gates": [ collect_gate("proposal_safety", safety), collect_gate("workspace_integrity", integrity), collect_gate("source_freshness", freshness), collect_gate("workspace_semantic_validation", semantic), collect_gate("workspace_diff", diff), ], "diff_summary": diff.get("counts"), "next_actions": next_actions(status), "details": { "safety": safety, "integrity": integrity, "freshness": freshness, "semantic": semantic, "diff": diff, }, } def main() -> int: parser = argparse.ArgumentParser(description="Run 1C patch workspace preflight.") parser.add_argument("--workspace", type=Path, required=True) parser.add_argument("--max-patch-chars", type=int, default=200000) parser.add_argument("--output", type=Path) args = parser.parse_args() result = build_preflight(args.workspace, max_patch_chars=args.max_patch_chars) output = json.dumps(result, ensure_ascii=False, indent=2) + "\n" if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(output, encoding="utf-8") print(json.dumps({"output": str(args.output) if args.output else None, "passed": result["passed"], "status": result["status"], "diff": result["diff_summary"]}, ensure_ascii=False)) return 0 if result["passed"] else 2 if __name__ == "__main__": raise SystemExit(main())