project-standalo-sonic-cloud/skills/guardrail-orchestrator/scripts/migrate_task_sessions.py

266 lines
9.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Migration script to convert flat task session files to directory structure.
This script migrates task sessions from the old flat file structure:
.workflow/versions/v001/task_sessions/task_design.yml
To the new directory structure:
.workflow/versions/v001/task_sessions/task_design/
session.yml
task.yml
operations.log
Usage:
python3 migrate_task_sessions.py [--dry-run]
Options:
--dry-run Show what would be done without making changes
"""
from __future__ import annotations
import sys
import argparse
from pathlib import Path
from datetime import datetime
from typing import Optional
# Add parent to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from version_manager import load_yaml, save_yaml, get_workflow_dir
# ============================================================================
# Discovery Functions
# ============================================================================
def find_flat_task_sessions() -> list[tuple[Path, str]]:
"""
Find all flat task session YAML files.
Returns:
List of tuples: (file_path, version_name)
"""
workflow_dir = get_workflow_dir()
versions_dir = workflow_dir / 'versions'
flat_sessions = []
if versions_dir.exists():
for version_dir in versions_dir.iterdir():
if version_dir.is_dir():
task_sessions_dir = version_dir / 'task_sessions'
if task_sessions_dir.exists():
for item in task_sessions_dir.iterdir():
# Check if it's a YAML file (not a directory)
if item.is_file() and item.suffix in ['.yml', '.yaml']:
flat_sessions.append((item, version_dir.name))
return flat_sessions
# ============================================================================
# Migration Functions
# ============================================================================
def migrate_task_session(file_path: Path, version: str, dry_run: bool = False) -> dict:
"""
Migrate a single flat task session to directory structure.
Args:
file_path: Path to the flat YAML file
version: Version identifier (e.g., 'v001')
dry_run: If True, only report what would be done
Returns:
Dictionary with migration results and actions taken
"""
task_id = file_path.stem # e.g., "task_design" from "task_design.yml"
parent_dir = file_path.parent
new_dir = parent_dir / task_id
result = {
'task_id': task_id,
'version': version,
'original_path': str(file_path),
'new_path': str(new_dir),
'success': False,
'actions': []
}
if dry_run:
result['actions'].append(f"Would create directory: {new_dir}")
result['actions'].append(f"Would move {file_path.name} to {new_dir}/session.yml")
result['actions'].append(f"Would create {new_dir}/task.yml (if source exists)")
result['actions'].append(f"Would create {new_dir}/operations.log")
result['success'] = True
return result
try:
# Create directory
new_dir.mkdir(exist_ok=True)
result['actions'].append(f"Created directory: {new_dir}")
# Move session file
session_data = load_yaml(str(file_path))
save_yaml(str(new_dir / 'session.yml'), session_data)
file_path.unlink() # Delete original
result['actions'].append(f"Moved session data to: {new_dir}/session.yml")
# Create task.yml snapshot (try to find original task)
task_file = Path('tasks') / f'{task_id}.yml'
if task_file.exists():
task_data = load_yaml(str(task_file))
task_data['snapshotted_at'] = datetime.now().isoformat()
task_data['source_path'] = str(task_file)
task_data['status_at_snapshot'] = task_data.get('status', 'migrated')
task_data['migration_note'] = 'Created during migration from flat file structure'
save_yaml(str(new_dir / 'task.yml'), task_data)
result['actions'].append(f"Created task snapshot: {new_dir}/task.yml")
else:
# Create minimal task.yml from session data
minimal_task = {
'id': task_id,
'type': session_data.get('task_type', 'unknown'),
'agent': session_data.get('agent', 'unknown'),
'snapshotted_at': datetime.now().isoformat(),
'source_path': 'N/A - reconstructed from session',
'status_at_snapshot': 'migrated',
'migration_note': 'Task file not found - reconstructed from session data'
}
save_yaml(str(new_dir / 'task.yml'), minimal_task)
result['actions'].append(f"Warning: Task file not found at {task_file}")
result['actions'].append(f"Created minimal task snapshot: {new_dir}/task.yml")
# Create operations.log
log_content = f"# Operations Log for {task_id}\n"
log_content += f"# Migrated: {datetime.now().isoformat()}\n"
log_content += "# Format: [timestamp] OPERATION target_type: target_id (path)\n"
log_content += "=" * 70 + "\n\n"
log_content += f"[{datetime.now().isoformat()}] MIGRATION: Converted from flat file structure\n"
# If session has operations, add them to the log
if 'operations' in session_data and session_data['operations']:
log_content += f"\n# Historical operations from session data:\n"
for op in session_data['operations']:
timestamp = op.get('performed_at', 'unknown')
op_type = op.get('type', 'UNKNOWN')
target_type = op.get('target_type', 'unknown')
target_id = op.get('target_id', 'unknown')
target_path = op.get('target_path', '')
entry = f"[{timestamp}] {op_type} {target_type}: {target_id}"
if target_path:
entry += f" ({target_path})"
diff_summary = op.get('changes', {}).get('diff_summary', '')
if diff_summary:
entry += f"\n Summary: {diff_summary}"
log_content += entry + "\n"
(new_dir / 'operations.log').write_text(log_content)
result['actions'].append(f"Created operations log: {new_dir}/operations.log")
result['success'] = True
except Exception as e:
result['error'] = str(e)
result['actions'].append(f"Error: {e}")
return result
# ============================================================================
# Main Entry Point
# ============================================================================
def main():
"""Main migration script entry point."""
parser = argparse.ArgumentParser(
description='Migrate task session files from flat structure to directories',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes'
)
args = parser.parse_args()
dry_run = args.dry_run
# Header
print("=" * 70)
print("Task Session Migration Script".center(70))
print(f"Mode: {'DRY RUN' if dry_run else 'LIVE MIGRATION'}".center(70))
print("=" * 70)
print()
# Find flat sessions
flat_sessions = find_flat_task_sessions()
if not flat_sessions:
print("No flat task session files found. Nothing to migrate.")
print()
print("This could mean:")
print(" 1. All task sessions are already migrated")
print(" 2. No task sessions exist yet")
print(" 3. .workflow directory doesn't exist")
return
print(f"Found {len(flat_sessions)} flat task session file(s) to migrate:")
print()
# Process each file
results = []
for file_path, version in flat_sessions:
print(f"Processing: {version}/{file_path.name}")
print("-" * 70)
result = migrate_task_session(file_path, version, dry_run)
results.append(result)
for action in result['actions']:
print(f" {action}")
if not result['success'] and 'error' in result:
print(f" ERROR: {result['error']}")
print()
# Summary
successful = sum(1 for r in results if r['success'])
failed = len(results) - successful
print("=" * 70)
print("Migration Summary".center(70))
print("=" * 70)
print(f"Total files processed: {len(results)}")
print(f"Successful migrations: {successful}")
print(f"Failed migrations: {failed}")
print()
if dry_run:
print("This was a DRY RUN. No files were modified.")
print("Run without --dry-run to perform the migration.")
else:
if successful > 0:
print("Migration completed successfully!")
print()
print("Next steps:")
print(" 1. Verify migrated files in .workflow/versions/*/task_sessions/")
print(" 2. Check that each task has session.yml, task.yml, and operations.log")
print(" 3. Test the system to ensure compatibility")
if failed > 0:
print()
print(f"WARNING: {failed} migration(s) failed. Review the errors above.")
print()
if __name__ == '__main__':
main()