Files
colleague-skill/tools/version_manager.py
titanwings 6a0b31aa6c refactor: restructure to official AgentSkills/Claude Code skill format
- Flatten colleague-creator/ to repo root (repo = skill directory)
- Update SKILL.md frontmatter with official fields: name, description,
  argument-hint, version, user-invocable, allowed-tools
- Move PRD.md → docs/PRD.md
- Add .gitignore, requirements.txt, LICENSE
- Update README and INSTALL docs to reflect new structure and git clone install

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 13:37:54 +08:00

155 lines
4.9 KiB
Python
Raw Permalink 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
"""
版本管理器
负责 Skill 文件的版本存档和回滚。
用法:
python version_manager.py --action list --slug zhangsan --base-dir ~/.openclaw/...
python version_manager.py --action rollback --slug zhangsan --version v2 --base-dir ~/.openclaw/...
"""
from __future__ import annotations
import json
import shutil
import argparse
import sys
from pathlib import Path
from datetime import datetime, timezone
MAX_VERSIONS = 10 # 最多保留的版本数
def list_versions(skill_dir: Path) -> list:
"""列出所有历史版本"""
versions_dir = skill_dir / "versions"
if not versions_dir.exists():
return []
versions = []
for v_dir in sorted(versions_dir.iterdir()):
if not v_dir.is_dir():
continue
# 从目录名解析版本号
version_name = v_dir.name
# 获取存档时间(用目录修改时间近似)
mtime = v_dir.stat().st_mtime
archived_at = datetime.fromtimestamp(mtime, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
# 统计文件
files = [f.name for f in v_dir.iterdir() if f.is_file()]
versions.append({
"version": version_name,
"archived_at": archived_at,
"files": files,
"path": str(v_dir),
})
return versions
def rollback(skill_dir: Path, target_version: str) -> bool:
"""回滚到指定版本"""
version_dir = skill_dir / "versions" / target_version
if not version_dir.exists():
print(f"错误:版本 {target_version} 不存在", file=sys.stderr)
return False
# 先存档当前版本
meta_path = skill_dir / "meta.json"
if meta_path.exists():
meta = json.loads(meta_path.read_text(encoding="utf-8"))
current_version = meta.get("version", "v?")
backup_dir = skill_dir / "versions" / f"{current_version}_before_rollback"
backup_dir.mkdir(parents=True, exist_ok=True)
for fname in ("SKILL.md", "work.md", "persona.md"):
src = skill_dir / fname
if src.exists():
shutil.copy2(src, backup_dir / fname)
# 从目标版本恢复文件
restored_files = []
for fname in ("SKILL.md", "work.md", "persona.md"):
src = version_dir / fname
if src.exists():
shutil.copy2(src, skill_dir / fname)
restored_files.append(fname)
# 更新 meta
if meta_path.exists():
meta = json.loads(meta_path.read_text(encoding="utf-8"))
meta["version"] = target_version + "_restored"
meta["updated_at"] = datetime.now(timezone.utc).isoformat()
meta["rollback_from"] = current_version
meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"已回滚到 {target_version},恢复文件:{', '.join(restored_files)}")
return True
def cleanup_old_versions(skill_dir: Path, max_versions: int = MAX_VERSIONS):
"""清理超出限制的旧版本"""
versions_dir = skill_dir / "versions"
if not versions_dir.exists():
return
# 按版本号排序,保留最新的 max_versions 个
version_dirs = sorted(
[d for d in versions_dir.iterdir() if d.is_dir()],
key=lambda d: d.stat().st_mtime,
)
to_delete = version_dirs[:-max_versions] if len(version_dirs) > max_versions else []
for old_dir in to_delete:
shutil.rmtree(old_dir)
print(f"已清理旧版本:{old_dir.name}")
def main():
parser = argparse.ArgumentParser(description="Skill 版本管理器")
parser.add_argument("--action", required=True, choices=["list", "rollback", "cleanup"])
parser.add_argument("--slug", required=True, help="同事 slug")
parser.add_argument("--version", help="目标版本号rollback 时使用)")
parser.add_argument(
"--base-dir",
default="~/.openclaw/workspace/skills/colleagues",
help="同事 Skill 根目录",
)
args = parser.parse_args()
base_dir = Path(args.base_dir).expanduser()
skill_dir = base_dir / args.slug
if not skill_dir.exists():
print(f"错误:找不到 Skill 目录 {skill_dir}", file=sys.stderr)
sys.exit(1)
if args.action == "list":
versions = list_versions(skill_dir)
if not versions:
print(f"{args.slug} 暂无历史版本")
else:
print(f"{args.slug} 的历史版本:\n")
for v in versions:
print(f" {v['version']} 存档时间: {v['archived_at']} 文件: {', '.join(v['files'])}")
elif args.action == "rollback":
if not args.version:
print("错误rollback 操作需要 --version", file=sys.stderr)
sys.exit(1)
rollback(skill_dir, args.version)
elif args.action == "cleanup":
cleanup_old_versions(skill_dir)
print("清理完成")
if __name__ == "__main__":
main()