实现本地 Markdown 清洗评审器
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user