mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-29 16:16:08 +00:00
286 lines
7.9 KiB
Python
286 lines
7.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Migrate SuperClaude components to Skills-based architecture
|
||
|
|
|
||
|
|
Converts always-loaded Markdown files to on-demand Skills loading
|
||
|
|
for 97-98% token savings at Claude Code startup.
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python scripts/migrate_to_skills.py --dry-run # Preview changes
|
||
|
|
python scripts/migrate_to_skills.py # Execute migration
|
||
|
|
python scripts/migrate_to_skills.py --rollback # Undo migration
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import shutil
|
||
|
|
from pathlib import Path
|
||
|
|
import sys
|
||
|
|
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
CLAUDE_DIR = Path.home() / ".claude"
|
||
|
|
SUPERCLAUDE_DIR = CLAUDE_DIR / "superclaude"
|
||
|
|
SKILLS_DIR = CLAUDE_DIR / "skills"
|
||
|
|
BACKUP_DIR = SUPERCLAUDE_DIR.parent / "superclaude.backup"
|
||
|
|
|
||
|
|
# Component mapping: superclaude path → skill name
|
||
|
|
COMPONENTS = {
|
||
|
|
# Agents
|
||
|
|
"agents/pm-agent.md": "pm",
|
||
|
|
"agents/task-agent.md": "task",
|
||
|
|
"agents/research-agent.md": "research",
|
||
|
|
"agents/brainstorm-agent.md": "brainstorm",
|
||
|
|
"agents/analyzer.md": "analyze",
|
||
|
|
|
||
|
|
# Modes
|
||
|
|
"modes/MODE_Orchestration.md": "orchestration-mode",
|
||
|
|
"modes/MODE_Brainstorming.md": "brainstorming-mode",
|
||
|
|
"modes/MODE_Introspection.md": "introspection-mode",
|
||
|
|
"modes/MODE_Task_Management.md": "task-management-mode",
|
||
|
|
"modes/MODE_Token_Efficiency.md": "token-efficiency-mode",
|
||
|
|
"modes/MODE_DeepResearch.md": "deep-research-mode",
|
||
|
|
"modes/MODE_Business_Panel.md": "business-panel-mode",
|
||
|
|
}
|
||
|
|
|
||
|
|
# Shared modules (copied to each skill that needs them)
|
||
|
|
SHARED_MODULES = [
|
||
|
|
"modules/git-status.md",
|
||
|
|
"modules/token-counter.md",
|
||
|
|
"modules/pm-formatter.md",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def create_skill_md(skill_name: str, original_file: Path) -> str:
|
||
|
|
"""Generate SKILL.md content from original file"""
|
||
|
|
|
||
|
|
# Extract frontmatter if exists
|
||
|
|
content = original_file.read_text()
|
||
|
|
lines = content.split("\n")
|
||
|
|
|
||
|
|
description = f"{skill_name.replace('-', ' ').title()} - Skills-based implementation"
|
||
|
|
|
||
|
|
# Try to extract description from frontmatter
|
||
|
|
if lines[0].strip() == "---":
|
||
|
|
for line in lines[1:10]:
|
||
|
|
if line.startswith("description:"):
|
||
|
|
description = line.split(":", 1)[1].strip().strip('"')
|
||
|
|
break
|
||
|
|
|
||
|
|
return f"""---
|
||
|
|
name: {skill_name}
|
||
|
|
description: {description}
|
||
|
|
version: 1.0.0
|
||
|
|
author: SuperClaude
|
||
|
|
migrated: true
|
||
|
|
---
|
||
|
|
|
||
|
|
# {skill_name.replace('-', ' ').title()}
|
||
|
|
|
||
|
|
Skills-based on-demand loading implementation.
|
||
|
|
|
||
|
|
**Token Efficiency**:
|
||
|
|
- Startup: 0 tokens (not loaded)
|
||
|
|
- Description: ~50-100 tokens
|
||
|
|
- Full load: ~2,500 tokens (when used)
|
||
|
|
|
||
|
|
**Activation**: `/sc:{skill_name}` or auto-triggered by context
|
||
|
|
|
||
|
|
**Implementation**: See `implementation.md` for full protocol
|
||
|
|
|
||
|
|
**Modules**: Additional support files in `modules/` directory
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def migrate_component(source_path: Path, skill_name: str, dry_run: bool = False) -> dict:
|
||
|
|
"""Migrate a single component to Skills structure"""
|
||
|
|
|
||
|
|
result = {
|
||
|
|
"skill": skill_name,
|
||
|
|
"source": str(source_path),
|
||
|
|
"status": "skipped",
|
||
|
|
"token_savings": 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
if not source_path.exists():
|
||
|
|
result["status"] = "source_missing"
|
||
|
|
return result
|
||
|
|
|
||
|
|
# Calculate token savings
|
||
|
|
word_count = len(source_path.read_text().split())
|
||
|
|
original_tokens = int(word_count * 1.3)
|
||
|
|
skill_tokens = 70 # SKILL.md description only
|
||
|
|
result["token_savings"] = original_tokens - skill_tokens
|
||
|
|
|
||
|
|
skill_dir = SKILLS_DIR / skill_name
|
||
|
|
|
||
|
|
if dry_run:
|
||
|
|
result["status"] = "would_migrate"
|
||
|
|
result["target"] = str(skill_dir)
|
||
|
|
return result
|
||
|
|
|
||
|
|
# Create skill directory
|
||
|
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
# Create SKILL.md
|
||
|
|
skill_md = skill_dir / "SKILL.md"
|
||
|
|
skill_md.write_text(create_skill_md(skill_name, source_path))
|
||
|
|
|
||
|
|
# Copy implementation
|
||
|
|
impl_md = skill_dir / "implementation.md"
|
||
|
|
shutil.copy2(source_path, impl_md)
|
||
|
|
|
||
|
|
# Copy modules if this is an agent
|
||
|
|
if "agents" in str(source_path):
|
||
|
|
modules_dir = skill_dir / "modules"
|
||
|
|
modules_dir.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
for module_path in SHARED_MODULES:
|
||
|
|
module_file = SUPERCLAUDE_DIR / module_path
|
||
|
|
if module_file.exists():
|
||
|
|
shutil.copy2(module_file, modules_dir / module_file.name)
|
||
|
|
|
||
|
|
result["status"] = "migrated"
|
||
|
|
result["target"] = str(skill_dir)
|
||
|
|
|
||
|
|
return result
|
||
|
|
|
||
|
|
|
||
|
|
def backup_superclaude(dry_run: bool = False) -> bool:
|
||
|
|
"""Create backup of current SuperClaude directory"""
|
||
|
|
|
||
|
|
if not SUPERCLAUDE_DIR.exists():
|
||
|
|
print(f"❌ SuperClaude directory not found: {SUPERCLAUDE_DIR}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if BACKUP_DIR.exists():
|
||
|
|
print(f"⚠️ Backup already exists: {BACKUP_DIR}")
|
||
|
|
print(" Skipping backup (use --force to overwrite)")
|
||
|
|
return True
|
||
|
|
|
||
|
|
if dry_run:
|
||
|
|
print(f"Would create backup: {SUPERCLAUDE_DIR} → {BACKUP_DIR}")
|
||
|
|
return True
|
||
|
|
|
||
|
|
print(f"Creating backup: {BACKUP_DIR}")
|
||
|
|
shutil.copytree(SUPERCLAUDE_DIR, BACKUP_DIR)
|
||
|
|
print("✅ Backup created")
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def rollback_migration() -> bool:
|
||
|
|
"""Restore from backup"""
|
||
|
|
|
||
|
|
if not BACKUP_DIR.exists():
|
||
|
|
print(f"❌ No backup found: {BACKUP_DIR}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print(f"Rolling back to backup...")
|
||
|
|
|
||
|
|
# Remove skills directory
|
||
|
|
if SKILLS_DIR.exists():
|
||
|
|
print(f"Removing skills: {SKILLS_DIR}")
|
||
|
|
shutil.rmtree(SKILLS_DIR)
|
||
|
|
|
||
|
|
# Restore superclaude
|
||
|
|
if SUPERCLAUDE_DIR.exists():
|
||
|
|
print(f"Removing current: {SUPERCLAUDE_DIR}")
|
||
|
|
shutil.rmtree(SUPERCLAUDE_DIR)
|
||
|
|
|
||
|
|
print(f"Restoring from backup...")
|
||
|
|
shutil.copytree(BACKUP_DIR, SUPERCLAUDE_DIR)
|
||
|
|
|
||
|
|
print("✅ Rollback complete")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(
|
||
|
|
description="Migrate SuperClaude to Skills-based architecture"
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--dry-run",
|
||
|
|
action="store_true",
|
||
|
|
help="Preview changes without executing"
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--rollback",
|
||
|
|
action="store_true",
|
||
|
|
help="Restore from backup"
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--no-backup",
|
||
|
|
action="store_true",
|
||
|
|
help="Skip backup creation (dangerous)"
|
||
|
|
)
|
||
|
|
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
# Rollback mode
|
||
|
|
if args.rollback:
|
||
|
|
success = rollback_migration()
|
||
|
|
sys.exit(0 if success else 1)
|
||
|
|
|
||
|
|
# Migration mode
|
||
|
|
print("=" * 60)
|
||
|
|
print("SuperClaude → Skills Migration")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
if args.dry_run:
|
||
|
|
print("🔍 DRY RUN MODE - No changes will be made\n")
|
||
|
|
|
||
|
|
# Backup
|
||
|
|
if not args.no_backup:
|
||
|
|
if not backup_superclaude(args.dry_run):
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
print(f"\nMigrating {len(COMPONENTS)} components...\n")
|
||
|
|
|
||
|
|
# Migrate components
|
||
|
|
results = []
|
||
|
|
total_savings = 0
|
||
|
|
|
||
|
|
for source_rel, skill_name in COMPONENTS.items():
|
||
|
|
source_path = SUPERCLAUDE_DIR / source_rel
|
||
|
|
result = migrate_component(source_path, skill_name, args.dry_run)
|
||
|
|
results.append(result)
|
||
|
|
|
||
|
|
status_icon = {
|
||
|
|
"migrated": "✅",
|
||
|
|
"would_migrate": "📋",
|
||
|
|
"source_missing": "⚠️",
|
||
|
|
"skipped": "⏭️",
|
||
|
|
}.get(result["status"], "❓")
|
||
|
|
|
||
|
|
print(f"{status_icon} {skill_name:25} {result['status']:15} "
|
||
|
|
f"(saves {result['token_savings']:,} tokens)")
|
||
|
|
|
||
|
|
total_savings += result["token_savings"]
|
||
|
|
|
||
|
|
# Summary
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("SUMMARY")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
migrated = sum(1 for r in results if r["status"] in ["migrated", "would_migrate"])
|
||
|
|
skipped = sum(1 for r in results if r["status"] in ["source_missing", "skipped"])
|
||
|
|
|
||
|
|
print(f"Migrated: {migrated}/{len(COMPONENTS)}")
|
||
|
|
print(f"Skipped: {skipped}/{len(COMPONENTS)}")
|
||
|
|
print(f"Total token savings: {total_savings:,} tokens")
|
||
|
|
print(f"Savings percentage: {total_savings * 100 // (total_savings + 500):.0f}%")
|
||
|
|
|
||
|
|
if args.dry_run:
|
||
|
|
print("\n💡 Run without --dry-run to execute migration")
|
||
|
|
else:
|
||
|
|
print(f"\n✅ Migration complete!")
|
||
|
|
print(f" Backup: {BACKUP_DIR}")
|
||
|
|
print(f" Skills: {SKILLS_DIR}")
|
||
|
|
print(f"\n Use --rollback to undo changes")
|
||
|
|
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main())
|