Files
deep_research/scripts/runtime/review.py
T

158 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Deterministic Phase 3 review checks for the Python core."""
from __future__ import annotations
import json
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from scripts.runtime.artifacts import load_manifest, write_manifest
from scripts.runtime.tasks import validate_packet
def utc_now_iso() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
def _source_ids_from_jsonl(path: Path) -> set[str]:
ids: set[str] = set()
if not path.exists():
return ids
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
try:
obj = json.loads(line)
except json.JSONDecodeError:
continue
source_id = obj.get("id") or obj.get("source_id")
if source_id:
ids.add(str(source_id))
return ids
def _draft_citations(drafts: list[Path]) -> set[str]:
cited: set[str] = set()
for draft in drafts:
cited.update(re.findall(r"\[(src_[A-Za-z0-9_-]+)\]", draft.read_text(encoding="utf-8")))
return cited
def _ready_packet_stems(project_root: Path) -> set[str]:
ready: set[str] = set()
for path in sorted((project_root / "phase2" / "packets").glob("*.json")):
try:
packet = json.loads(path.read_text(encoding="utf-8"))
validate_packet(packet)
except Exception:
continue
ready.add(path.stem)
return ready
def _draft_quality_findings(drafts: list[Path]) -> list[dict[str, Any]]:
findings: list[dict[str, Any]] = []
generic_markers = [
"需要进一步完善",
"应当加强",
"持续改进",
"系统性",
"闭环管理",
"质量文化",
]
for draft in drafts:
text = draft.read_text(encoding="utf-8")
zh_chars = sum(1 for char in text if "\u4e00" <= char <= "\u9fff")
citations = re.findall(r"\[(src_[A-Za-z0-9_-]+)\]", text)
evidence_table_present = "证据落点" in text and "待补证据" in text
if zh_chars >= 1200 and len(set(citations)) < 5:
findings.append({"severity": "P1", "message": f"{draft.name} 引用来源过少,可能未充分使用 evidence packet。"})
if zh_chars >= 1200 and not evidence_table_present:
findings.append({"severity": "P1", "message": f"{draft.name} 缺少“证据落点与待补证据”小节,难以判断 evidence 是否真正落到纸面。"})
generic_count = sum(text.count(marker) for marker in generic_markers)
if zh_chars >= 1200 and generic_count >= 18:
findings.append({"severity": "P1", "message": f"{draft.name} 泛化管理表述过多,需要回炉为具体审计发现、风险影响和整改动作。"})
return findings
def build_phase3_critique(project_root: Path) -> Path:
manifest = load_manifest(project_root)
drafts = sorted((project_root / "phase2" / "drafts").glob("ch*.md"))
packets = sorted((project_root / "phase2" / "packets").glob("*.json"))
ready_stems = _ready_packet_stems(project_root)
packet_errors = [
path for path in sorted((project_root / "phase2" / "packet_errors").glob("*.json"))
if path.stem not in ready_stems
]
chapter_errors = sorted((project_root / "phase2" / "chapter_errors").glob("*.json"))
sources = _source_ids_from_jsonl(project_root / "phase2" / "sources.jsonl")
cited = _draft_citations(drafts)
missing_sources = sorted(cited - sources) if sources else sorted(cited)
uncited_sources = sorted(sources - cited) if cited else sorted(sources)
findings: list[dict[str, Any]] = []
if not drafts:
findings.append({"severity": "P1", "message": "Phase 2 drafts 缺失,尚不能进入 Phase 4 成稿。"})
if packet_errors:
findings.append({"severity": "P1", "message": f"存在 {len(packet_errors)} 个 packet 失败,需要回炉补证据。"})
if chapter_errors:
findings.append({"severity": "P1", "message": f"存在 {len(chapter_errors)} 个章节组装失败,需要修复引用或重写该章。"})
quality_holds = manifest.get("quality_holds") or []
if quality_holds:
findings.append({"severity": "P1", "message": "存在质量暂停标记:" + ", ".join(quality_holds)})
findings.extend(_draft_quality_findings(drafts))
if missing_sources:
findings.append({"severity": "P1", "message": f"正文引用未在 sources.jsonl 中登记:{', '.join(missing_sources)}"})
if not findings:
findings.append({"severity": "P2", "message": "基础产物完整;仍需人工或大上下文模型审校逻辑链、反方证据和章节叙事。"})
lines = [
"# Phase 3 审校 critique",
"",
f"- 项目:{manifest.get('topic', project_root.name)}",
f"- 运行时:python-core-v0.20",
f"- drafts{len(drafts)}",
f"- packets{len(packets)}",
f"- sources{len(sources)}",
f"- cited_source_ids{', '.join(sorted(cited)) if cited else '无'}",
"",
"## Findings",
"",
]
for item in findings:
lines.append(f"- [{item['severity']}] {item['message']}")
lines.extend(
[
"",
"## Residual Risks",
"",
"- 本 deterministic review 只做结构、引用和错误包检查;深层逻辑审校仍建议交给 `phase3_review` 角色执行。",
"- 若 sources 为空,本审校会把所有正文引用视为待登记来源。",
"",
"## Next",
"",
"- 若存在 P1,先回到 Phase 2 修复 packet/chapter 错误。",
"- 若仅有 P2,可进入 `dr.py finalize` 的中文原生成稿路径。",
"",
]
)
out = project_root / "phase3" / "critique.md"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text("\n".join(lines), encoding="utf-8")
phase3 = manifest.setdefault("phase3", {})
phase3.update(
{
"status": "completed",
"critique_path": "phase3/critique.md",
"findings_total": len(findings),
"missing_sources": missing_sources,
"uncited_sources": uncited_sources,
"updated_at": utc_now_iso(),
}
)
manifest["updated_at"] = utc_now_iso()
write_manifest(project_root, manifest)
return out