from __future__ import annotations import json from hashlib import sha256 from pathlib import Path from typing import Any, TypedDict, cast class ReviewFixture(TypedDict): run_directory: Path source_path: Path manifest_path: Path locator_path: Path result_path: Path original: str cleaned: str def digest(text: str) -> str: return sha256(text.encode()).hexdigest() def write_json(path: Path, payload: object) -> None: path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def read_json(path: Path) -> dict[str, Any]: return cast(dict[str, Any], json.loads(path.read_text(encoding="utf-8"))) def create_review_run( root: Path, *, status: str = "success", with_locator: bool = True, ) -> ReviewFixture: run_directory = root / "artifacts/2026-08-23/runs/review-run" document_directory = run_directory / "documents/paper" document_directory.mkdir(parents=True) source_path = root / "paper.md" original = "\ufeff😀 old\r\nCafe\u0301\n" cleaned = "\ufeff😀 new\r\nCafe\u0301\n" source_path.write_text(original, encoding="utf-8") input_hash = digest(original) current_hash = digest(cleaned) if status == "success" else input_hash changes: list[dict[str, object]] = [] if status == "success": changes.append( { "component_id": "paper.rule", "component_version": "1.0.0", "component_position": 0, "proposal_ref": { "component_position": 0, "snapshot_sha256": input_hash, "proposal_index": 0, }, "edit_index": 0, "reason": "替换测试单词", "span": {"start": 3, "end": 6}, "location": {"line": 1, "column": 4}, "before": "old", "after": "new", "before_sha256": input_hash, "after_sha256": current_hash, } ) errors: list[dict[str, object]] = [] if status == "failed": errors.append( { "component_id": "paper.rule", "component_version": "1.0.0", "component_position": 0, "stage": "transform", "error_type": "SyntheticError", "message": "测试组件失败。", } ) residuals: list[dict[str, object]] = [] if status == "unstable": residuals.append( { "component_id": "paper.rule", "component_version": "1.0.0", "component_position": 0, "proposal_ref": { "component_position": 0, "snapshot_sha256": input_hash, "proposal_index": 0, }, "proposal": { "snapshot_sha256": input_hash, "reason": "仍可替换测试单词", "edits": [ { "snapshot_sha256": input_hash, "span": {"start": 3, "end": 6}, "expected_text": "old", "replacement": "new", } ], }, } ) result = { "schema_version": 1, "document": {"document_id": "paper", "source_label": "inputs/paper.md"}, "status": status, "input_sha256": input_hash, "current_sha256": current_hash, "changes": changes, "errors": errors, "residual_proposals": residuals, "output": { "cleaned_path": "cleaned.md" if status == "success" else None, "diff_path": "changes.diff" if status == "success" else None, }, } result_path = document_directory / "result.json" write_json(result_path, result) if status == "success": (document_directory / "cleaned.md").write_text(cleaned, encoding="utf-8") (document_directory / "changes.diff").write_text("synthetic diff\n", encoding="utf-8") manifest = { "schema_version": 1, "run": { "run_id": "review-run", "run_date": "2026-08-23", "utc_offset": "+08:00", "status": status, "started_at_utc": "2026-08-23T01:00:00Z", "completed_at_utc": "2026-08-23T01:01:00Z", "retention_until": "2026-09-22T01:01:00Z", }, "tool": { "name": "mdpolish", "package_version": "0.1.0", "python_version": "3.13.11", "platform": "linux-x86_64", "git_commit": None, "git_dirty": None, }, "pipeline": { "components": [ { "component_id": "paper.rule", "version": "1.0.0", "parameters": [], "applicability": "替换测试单词。", }, { "component_id": "paper.zero", "version": "1.0.0", "parameters": [], "applicability": "不修改当前测试文档。", }, ] }, "documents": [ { "document_id": "paper", "source_label": "inputs/paper.md", "status": status, "input_sha256": input_hash, "current_sha256": current_hash, "change_count": len(changes), "result_path": "documents/paper/result.json", "cleaned_path": "documents/paper/cleaned.md" if status == "success" else None, "diff_path": "documents/paper/changes.diff" if status == "success" else None, } ], "summary": { "document_count": 1, "success_count": int(status == "success"), "failed_count": int(status == "failed"), "unstable_count": int(status == "unstable"), "change_count": len(changes), }, } manifest_path = run_directory / "manifest.json" write_json(manifest_path, manifest) locator_path = run_directory / "review-locator.json" if with_locator: write_json( locator_path, { "schema_version": 1, "run": { "run_id": "review-run", "run_directory": str(run_directory.resolve()), "manifest_path": "manifest.json", }, "documents": [ { "document_id": "paper", "source_path": str(source_path.resolve()), "input_sha256": input_hash, } ], }, ) return { "run_directory": run_directory, "source_path": source_path, "manifest_path": manifest_path, "locator_path": locator_path, "result_path": result_path, "original": original, "cleaned": cleaned, }