213 lines
7.1 KiB
Python
213 lines
7.1 KiB
Python
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,
|
|
}
|