Files
pp-qa-km/tools/selfcheck_lint.py

116 lines
5.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.
"""Phase 4 自檢沙盒植入各類違規schema 缺欄位、壞 source_ref、過期頁、
攝入缺口、needs_ocr、孤兒頁、相似頁對驗證 lint 全部抓到、產出報告、
且不修改任何檔案。LLM 比對以假 LLM 驗證矛盾偵測路徑。
執行:.venv/Scripts/python tools/selfcheck_lint.py
"""
import json
import pathlib
import shutil
import sys
import tempfile
sys.path.insert(0, str(pathlib.Path(__file__).parent))
import kb
import lint
CFG = """ollama: {base_url: "http://localhost:11434", timeout_seconds: 5}
tasks:
ingest: {model: "fake:test", temperature: 0.2, max_retries: 2}
lint: {model: "fake:test", temperature: 0.1, max_retries: 2}
fallback: {model: "fake:fb"}
limits: {mcp_response_max_tokens: 2000, ingest_chunk_max_chars: 12000}
"""
SHA_A = "abcd1234" + "0" * 56
SHA_B = "beef5678" + "0" * 56
def page(root, rel, meta, body="內文。"):
p = root / rel
p.write_text(kb.dump_page(meta, body), encoding="utf-8")
def build(root):
import datetime
today = datetime.date.today().isoformat()
for sub in ("config", "raw/converted", "wiki/summaries", "wiki/entities", "wiki/concepts"):
(root / sub).mkdir(parents=True)
(root / "config/models.yaml").write_text(CFG, encoding="utf-8")
(root / "raw/manifest.json").write_text(json.dumps({"version": 1, "files": {
"raw/converted/good-src.md": {"kind": "converted", "sha256": SHA_A,
"original_path": "raw/originals/g.docx",
"original_sha256": SHA_A, "converter": "from_docx",
"converted_at": "2026-07-01T00:00:00"},
"raw/converted/uncovered.md": {"kind": "converted", "sha256": SHA_B,
"original_path": "raw/originals/u.docx",
"original_sha256": SHA_B, "converter": "from_docx",
"converted_at": "2026-07-01T00:00:00"},
"raw/originals/scan.pdf": {"kind": "original", "sha256": SHA_B,
"media_type": "pdf", "added_at": "2026-07-01T00:00:00",
"status": "needs_ocr"},
}}), encoding="utf-8")
ref = f"raw/converted/good-src.md#{SHA_A[:8]}"
good = {"type": "entity", "title": "支付閘道", "description": "支付閘道模組",
"tags": ["gateway"], "timestamp": today, "sources": [ref], "status": "draft"}
page(root, "wiki/entities/good.md", good)
bad = {"type": "entity", "title": "壞頁", "description": "缺 tags",
"timestamp": today, "sources": ["raw/converted/ghost.md#deadbeef"],
"status": "draft", "type": "entity"}
page(root, "wiki/entities/bad-schema.md", bad)
old = dict(good, title="舊概念", type="concept", timestamp="2024-01-01")
page(root, "wiki/concepts/old.md", old)
dup_a = dict(good, title="支付閘道逾時缺陷", tags=["gateway", "timeout"],
description="逾時缺陷模式")
dup_b = dict(good, title="支付閘道逾時缺陷模式", tags=["gateway", "timeout"],
description="逾時的缺陷模式")
page(root, "wiki/entities/dup-a.md", dup_a, "重試機制在峰值失效。")
page(root, "wiki/entities/dup-b.md", dup_b, "重試機制在峰值一律生效。")
(root / "index.md").write_text(
"- [支付閘道](wiki/entities/good.md)\n- [舊概念](wiki/concepts/old.md)\n"
"- [A](wiki/entities/dup-a.md)\n- [B](wiki/entities/dup-b.md)\n", encoding="utf-8")
def run_lint(args):
try:
lint.main(args)
return 0
except SystemExit as e:
return e.code
def main():
tmp = pathlib.Path(tempfile.mkdtemp(prefix="ppqa-lint-"))
real = kb.llm_json
try:
root = tmp / "repo"
build(root)
before = {p: p.read_bytes() for p in root.rglob("*.md")}
code = run_lint(["--no-llm", "--root", str(root)])
assert code == 1, code
report = next((root / "reports").glob("lint-*.md")).read_text(encoding="utf-8")
for expected in ("缺欄位 tags", "source_ref 不在 manifest",
"old.md", "建議人工複審", "uncovered.md", "攝入缺口",
"needs_ocr", "bad-schema.md` — 不被 index.md"):
assert expected in report, f"報告缺少:{expected}\n{report}"
assert "good.md" not in report
after = {p: p.read_bytes() for p in root.rglob("*.md") if "reports" not in str(p)}
assert all(before[p] == after[p] for p in after), "lint 修改了檔案!"
print("PASS: schema/stale/coverage/orphan 全抓到,報告產出,檔案零修改")
kb.llm_json = lambda cfg, task, prompt, validate: (
{"relation": "contradiction", "explanation": "重試機制生效與否說法相反"}, "fake:test")
code = run_lint(["--root", str(root)])
assert code == 1
report = sorted((root / "reports").glob("lint-*.md"))[-1].read_text(encoding="utf-8")
assert "疑似矛盾" in report and "dup-a.md ↔ wiki/entities/dup-b.md" in report, report
print("PASS: LLM 矛盾頁偵測Phase 6 成功標準的機制驗證)")
print("ALL PASS")
finally:
kb.llm_json = real
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == "__main__":
main()