mirror of
https://github.com/xr843/Master-skill.git
synced 2026-05-10 05:16:25 +00:00
89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
"""
|
|
Version Manager — archives and rolls back teacher skill versions.
|
|
Adapted from colleague-skill's version_manager.py.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
from datetime import datetime
|
|
|
|
MAX_VERSIONS = 10
|
|
|
|
|
|
def list_versions(teacher_dir: str) -> list:
|
|
"""List all archived versions of a teacher skill."""
|
|
versions_dir = os.path.join(teacher_dir, "versions")
|
|
if not os.path.exists(versions_dir):
|
|
return []
|
|
|
|
versions = []
|
|
for entry in sorted(os.listdir(versions_dir)):
|
|
entry_path = os.path.join(versions_dir, entry)
|
|
if os.path.isdir(entry_path) and entry.startswith("v"):
|
|
files = os.listdir(entry_path)
|
|
mtime = os.path.getmtime(entry_path)
|
|
versions.append({
|
|
"version": entry[1:],
|
|
"archived_at": datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M"),
|
|
"files": files,
|
|
})
|
|
return versions
|
|
|
|
|
|
def rollback(teacher_dir: str, target_version: str) -> bool:
|
|
"""Roll back a teacher skill to a previous version. Backs up current state first."""
|
|
target_dir = os.path.join(teacher_dir, "versions", f"v{target_version}")
|
|
if not os.path.isdir(target_dir):
|
|
return False
|
|
|
|
meta_path = os.path.join(teacher_dir, "meta.json")
|
|
current_version = "unknown"
|
|
if os.path.exists(meta_path):
|
|
with open(meta_path, "r", encoding="utf-8") as f:
|
|
meta = json.load(f)
|
|
current_version = meta.get("version", "unknown")
|
|
backup_dir = os.path.join(teacher_dir, "versions", f"v{current_version}_before_rollback")
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
for fname in ["SKILL.md", "teaching.md", "voice.md", "meta.json"]:
|
|
src = os.path.join(teacher_dir, fname)
|
|
if os.path.exists(src):
|
|
shutil.copy2(src, backup_dir)
|
|
|
|
for fname in ["SKILL.md", "teaching.md", "voice.md", "meta.json"]:
|
|
src = os.path.join(target_dir, fname)
|
|
if os.path.exists(src):
|
|
shutil.copy2(src, os.path.join(teacher_dir, fname))
|
|
|
|
if os.path.exists(meta_path):
|
|
with open(meta_path, "r", encoding="utf-8") as f:
|
|
meta = json.load(f)
|
|
meta["rollback_from"] = current_version
|
|
meta["updated_at"] = datetime.now().strftime("%Y-%m-%d")
|
|
with open(meta_path, "w", encoding="utf-8") as f:
|
|
json.dump(meta, f, ensure_ascii=False, indent=2)
|
|
|
|
return True
|
|
|
|
|
|
def cleanup_old_versions(teacher_dir: str) -> int:
|
|
"""Remove old versions beyond MAX_VERSIONS limit."""
|
|
versions_dir = os.path.join(teacher_dir, "versions")
|
|
if not os.path.exists(versions_dir):
|
|
return 0
|
|
|
|
entries = []
|
|
for entry in os.listdir(versions_dir):
|
|
entry_path = os.path.join(versions_dir, entry)
|
|
if os.path.isdir(entry_path):
|
|
entries.append((entry_path, os.path.getmtime(entry_path)))
|
|
|
|
entries.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
removed = 0
|
|
for path, _ in entries[MAX_VERSIONS:]:
|
|
shutil.rmtree(path)
|
|
removed += 1
|
|
|
|
return removed
|