122 lines
5.8 KiB
Python
122 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
||
"""update_flow.py — `cangjie.py update` 的编排逻辑(Phase 2B,路线 C)。
|
||
|
||
CLI 只做确定性部分:登记来源 → 切块 → diff → change-set → 影响分析。
|
||
需要语义判断的节点(增量候选提取、correction/contradiction 定性、合并规则应用)
|
||
生成待处理 Agent 任务清单,由 Agent 按 methodology 完成后再交回 CLI 校验与重编译。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
from build_chunks import build as build_chunks_for # noqa: E402
|
||
from cangjie_common import create_run_workdir, load_yaml, new_run_id, sha256_file # noqa: E402
|
||
from diff_sources import diff as diff_chunks, load_chunks # noqa: E402
|
||
from impact_analysis import analyze, build_graph # noqa: E402
|
||
from cangjie_common import dump_json # noqa: E402
|
||
|
||
|
||
def ensure_manifest(pack: Path) -> dict:
|
||
manifest_path = pack / ".cangjie" / "manifest.yaml"
|
||
if manifest_path.exists():
|
||
return load_yaml(manifest_path)
|
||
manifest = {"schema_version": 1, "content_pack": pack.name, "sources": []}
|
||
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
||
manifest_path.write_text(yaml.safe_dump(manifest, allow_unicode=True, sort_keys=False), encoding="utf-8")
|
||
return manifest
|
||
|
||
|
||
def run_update(pack: Path, new_source: Path) -> int:
|
||
sidecar = pack / ".cangjie"
|
||
bundle_dir = sidecar / "capabilities"
|
||
if not (bundle_dir / "verified.yaml").exists():
|
||
raise SystemExit(f"未找到 Capability Bundle: {bundle_dir}/verified.yaml(旧 pack 先运行 migrate-legacy)")
|
||
|
||
run_id = new_run_id()
|
||
workdir = create_run_workdir(sidecar, run_id)
|
||
|
||
# 1. 登记来源版本
|
||
manifest_path = sidecar / "manifest.yaml"
|
||
manifest = ensure_manifest(pack)
|
||
source_id = f"src-{new_source.stem.lower().replace(' ', '-')[:32]}"
|
||
version_id = f"sha256:{sha256_file(new_source)}"
|
||
entry = next((s for s in manifest["sources"] if s["source_id"] == source_id), None)
|
||
if entry is None:
|
||
entry = {"source_id": source_id, "kind": "other", "title": new_source.stem,
|
||
"uri": new_source.resolve().as_uri(), "rights": "user-provided", "trust": "primary",
|
||
"versions": []}
|
||
manifest["sources"].append(entry)
|
||
if any(v["version_id"] == version_id for v in entry["versions"]):
|
||
print(f"[skip] 该来源版本已登记(exact duplicate): {version_id[:23]}…")
|
||
return 0
|
||
for v in entry["versions"]:
|
||
if v["status"] == "active":
|
||
v["status"] = "superseded"
|
||
entry["versions"].append({"version_id": version_id, "added_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||
"parser": "build_chunks v1.0", "status": "active"})
|
||
manifest_path.write_text(yaml.safe_dump(manifest, allow_unicode=True, sort_keys=False), encoding="utf-8")
|
||
|
||
# 2. 切块(旧版本块先备份用于 diff)
|
||
chunks_path = sidecar / "chunks" / "chunks.jsonl"
|
||
old_chunks_backup = None
|
||
if chunks_path.exists():
|
||
old_chunks_backup = workdir / "old-chunks.jsonl"
|
||
shutil.copyfile(chunks_path, old_chunks_backup)
|
||
build_chunks_for(new_source, sidecar, source_id, 4000)
|
||
|
||
# 3. diff → change-set
|
||
old = load_chunks(old_chunks_backup) if old_chunks_backup else []
|
||
new = load_chunks(chunks_path)
|
||
changes = diff_chunks(old, new)
|
||
change_set = {
|
||
"schema_version": 1,
|
||
"change_id": f"chg-{run_id}",
|
||
"content_pack": pack.name,
|
||
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||
"base_version": old[0]["version_id"] if old else None,
|
||
"new_version": version_id,
|
||
"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": len({c["content_hash"] for c in old} & {c["content_hash"] for c in new}),
|
||
},
|
||
}
|
||
cs_path = sidecar / "changes" / f"{change_set['change_id']}.json"
|
||
dump_json(cs_path, change_set)
|
||
|
||
# 4. 影响分析
|
||
graph = build_graph(bundle_dir, chunks_path)
|
||
dump_json(sidecar / "graph" / "dependencies.json", graph)
|
||
report = analyze(graph, change_set)
|
||
(workdir / "impact-report.md").write_text(report, encoding="utf-8")
|
||
|
||
# 5. 生成待处理 Agent 任务(语义节点不由 CLI 伪造)
|
||
pending = [f"# update 待处理任务(run: {run_id})", "",
|
||
f"change-set: `{cs_path}`;影响分析: `{workdir / 'impact-report.md'}`", "",
|
||
"按 `methodology/` 合并规则逐项处理(CLI 不做语义判断):", ""]
|
||
need_confirm = [c for c in changes if c.get("requires_human_confirmation")]
|
||
additive = [c for c in changes if c["change_type"] == "additive"]
|
||
if additive:
|
||
pending.append(f"1. **增量候选提取**:对 {len(additive)} 个新增块跑阶段 1(可用检索式取块),"
|
||
"新候选走阶段 1.5 三重验证 → 阶段 1.6 晋级门 → 更新 Bundle;")
|
||
if need_confirm:
|
||
pending.append(f"2. **人工确认项({len(need_confirm)})**:modified/deletion 块需定性 "
|
||
"correction / contradiction / near_duplicate,冲突不得静默综合(§6.4);")
|
||
pending += ["3. Bundle 更新后运行 `cangjie.py compile`(沿用原输出策略)重编译受影响入口;",
|
||
"4. 运行受影响能力 + 邻居能力的回归评测(见影响分析报告)。", "",
|
||
"> 未受影响的能力/入口不重编译,文件哈希保持不变。"]
|
||
(workdir / "pending-tasks.md").write_text("\n".join(pending) + "\n", encoding="utf-8")
|
||
|
||
print(report)
|
||
print(f"待处理任务: {workdir / 'pending-tasks.md'}")
|
||
return 0
|