#!/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()