mirror of
https://github.com/xr843/Master-skill.git
synced 2026-05-10 13:26:25 +00:00
feat: add version manager for teacher skill archival and rollback
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user