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

122 lines
5.8 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
"""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