Files
playbook/cangjie-skill/scripts/diff_sources.py
T
2026-08-31 05:00:46 +08:00

123 lines
4.7 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.
#!/usr/bin/env python3
"""diff_sources.py — 两个版本的块清单 → change-setPhase 2B,纯确定性)。
判定规则(确定性部分):
- content_hash 相同 → unchanged / exact_duplicate(同版本内重复出现时)
- 同 heading_path 且哈希不同 → modified(是否为 correction/contradiction 属语义判断,
标记 requires_human_confirmation 交给 Agent 复核)
- 新版本独有 → additive
- 旧版本独有 → deletion(不物理删除历史证据,只进影响分析)
near_duplicate / correction / contradiction 的语义定性不在本脚本伪造——路线 C 下由 Agent
按 methodology 合并规则复核后回填 change-set。
用法: python3 scripts/diff_sources.py <old-chunks.jsonl> <new-chunks.jsonl> --out <change-set.json> [--pack <slug>]
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from cangjie_common import dump_json # noqa: E402
def load_chunks(path: Path) -> list[dict]:
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
def diff(old: list[dict], new: list[dict]) -> list[dict]:
old_by_hash = {c["content_hash"]: c for c in old}
new_by_hash = {c["content_hash"]: c for c in new}
changes: list[dict] = []
unchanged_hashes = old_by_hash.keys() & new_by_hash.keys()
old_rest = [c for c in old if c["content_hash"] not in unchanged_hashes]
new_rest = [c for c in new if c["content_hash"] not in unchanged_hashes]
# 同 heading_path 配对为 modified
old_by_path: dict[str, list[dict]] = {}
for c in old_rest:
old_by_path.setdefault(" / ".join(c["heading_path"]), []).append(c)
for c in new_rest:
path_key = " / ".join(c["heading_path"])
pool = old_by_path.get(path_key)
if pool:
o = pool.pop(0)
changes.append({
"change_type": "modified",
"chunk_id": c["chunk_id"],
"old_hash": o["content_hash"],
"new_hash": c["content_hash"],
"heading_path": c["heading_path"],
"requires_human_confirmation": True,
"note": "同章节内容变化;是否为 correction/contradiction 需 Agent 语义复核",
})
else:
changes.append({
"change_type": "additive",
"chunk_id": c["chunk_id"],
"old_hash": None,
"new_hash": c["content_hash"],
"heading_path": c["heading_path"],
"requires_human_confirmation": False,
"note": "新增内容,进入影响分析与增量候选提取",
})
for pool in old_by_path.values():
for o in pool:
changes.append({
"change_type": "deletion",
"chunk_id": o["chunk_id"],
"old_hash": o["content_hash"],
"new_hash": None,
"heading_path": o["heading_path"],
"requires_human_confirmation": True,
"note": "来源撤回/删除;不立即物理删除历史证据,先计算受影响能力",
})
return changes
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("old_chunks")
ap.add_argument("new_chunks")
ap.add_argument("--out", required=True)
ap.add_argument("--pack", default="")
args = ap.parse_args()
old = load_chunks(Path(args.old_chunks))
new = load_chunks(Path(args.new_chunks))
changes = diff(old, new)
unchanged = len({c["content_hash"] for c in old} & {c["content_hash"] for c in new})
change_set = {
"schema_version": 1,
"change_id": time.strftime("chg-%Y%m%d-%H%M%S"),
"content_pack": args.pack,
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
"base_version": old[0]["version_id"] if old else None,
"new_version": new[0]["version_id"] if new else None,
"changes": changes,
"summary": {
"added": sum(1 for c in changes if c["change_type"] == "additive"),
"removed": sum(1 for c in changes if c["change_type"] == "deletion"),
"modified": sum(1 for c in changes if c["change_type"] == "modified"),
"unchanged": unchanged,
},
}
dump_json(Path(args.out), change_set)
s = change_set["summary"]
print(f"change-set: {args.out}+{s['added']} / -{s['removed']} / ~{s['modified']} / ={s['unchanged']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())