#!/usr/bin/env python3 """ Workflow versioning system with task session tracking. Links workflow sessions with task sessions and individual operations. """ from __future__ import annotations import argparse import hashlib import json import os import shutil import sys from datetime import datetime from pathlib import Path from typing import Optional # Try to import yaml try: import yaml HAS_YAML = True except ImportError: HAS_YAML = False # ============================================================================ # YAML/JSON Helpers # ============================================================================ def load_yaml(filepath: str) -> dict: """Load YAML file.""" if not os.path.exists(filepath): return {} with open(filepath, 'r') as f: content = f.read() if not content.strip(): return {} if HAS_YAML: return yaml.safe_load(content) or {} # Simple YAML fallback parser for basic key: value structures return parse_simple_yaml(content) def parse_simple_yaml(content: str) -> dict: """Parse simple YAML without PyYAML dependency.""" result = {} current_key = None current_list = None for line in content.split('\n'): stripped = line.strip() # Skip empty lines and comments if not stripped or stripped.startswith('#'): continue # Handle list items if stripped.startswith('- '): if current_list is not None: value = stripped[2:].strip() # Handle quoted strings if (value.startswith('"') and value.endswith('"')) or \ (value.startswith("'") and value.endswith("'")): value = value[1:-1] current_list.append(value) continue # Handle key: value if ':' in stripped: key, _, value = stripped.partition(':') key = key.strip() value = value.strip() # Check if this is a list start if value == '' or value == '[]': current_key = key current_list = [] result[key] = current_list elif value == '{}': result[key] = {} current_list = None elif value == 'null' or value == '~': result[key] = None current_list = None elif value == 'true': result[key] = True current_list = None elif value == 'false': result[key] = False current_list = None elif value.isdigit(): result[key] = int(value) current_list = None else: # Handle quoted strings if (value.startswith('"') and value.endswith('"')) or \ (value.startswith("'") and value.endswith("'")): value = value[1:-1] result[key] = value current_list = None return result def save_yaml(filepath: str, data: dict): """Save data to YAML file.""" os.makedirs(os.path.dirname(filepath), exist_ok=True) if HAS_YAML: with open(filepath, 'w') as f: yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) else: with open(filepath, 'w') as f: json.dump(data, f, indent=2) def file_hash(filepath: str) -> str: """Get SHA256 hash of file content.""" if not os.path.exists(filepath): return None with open(filepath, 'rb') as f: return hashlib.sha256(f.read()).hexdigest()[:16] # ============================================================================ # Path Helpers # ============================================================================ def get_workflow_dir() -> Path: return Path('.workflow') def get_versions_dir() -> Path: return get_workflow_dir() / 'versions' def get_index_path() -> Path: return get_workflow_dir() / 'index.yml' def get_operations_log_path() -> Path: return get_workflow_dir() / 'operations.log' def get_version_dir(version: str) -> Path: return get_versions_dir() / version def get_current_state_path() -> Path: return get_workflow_dir() / 'current.yml' def get_version_tasks_dir(version: str) -> Path: """Get the tasks directory for a specific version.""" return get_version_dir(version) / 'tasks' def get_current_tasks_dir() -> Optional[Path]: """Get the tasks directory for the currently active version.""" current_path = get_current_state_path() if not current_path.exists(): return None current = load_yaml(str(current_path)) version = current.get('active_version') if not version: return None tasks_dir = get_version_tasks_dir(version) tasks_dir.mkdir(parents=True, exist_ok=True) return tasks_dir # ============================================================================ # Version Index Management # ============================================================================ def load_index() -> dict: """Load or create version index.""" index_path = get_index_path() if index_path.exists(): return load_yaml(str(index_path)) return { 'versions': [], 'latest_version': None, 'total_versions': 0 } def save_index(index: dict): """Save version index.""" save_yaml(str(get_index_path()), index) def get_next_version() -> str: """Get next version number.""" index = load_index() return f"v{index['total_versions'] + 1:03d}" # ============================================================================ # Workflow Session Management # ============================================================================ def create_workflow_session(feature: str, parent_version: str = None) -> dict: """Create a new workflow session with version tracking.""" now = datetime.now() version = get_next_version() session_id = f"workflow_{now.strftime('%Y%m%d_%H%M%S')}" # Create version directory and tasks subdirectory version_dir = get_version_dir(version) version_dir.mkdir(parents=True, exist_ok=True) (version_dir / 'tasks').mkdir(exist_ok=True) # Create workflow session session = { 'version': version, 'feature': feature, 'session_id': session_id, 'parent_version': parent_version, 'status': 'pending', 'started_at': now.isoformat(), 'completed_at': None, 'current_phase': 'INITIALIZING', 'approvals': { 'design': { 'status': 'pending', 'approved_by': None, 'approved_at': None, 'rejection_reason': None }, 'implementation': { 'status': 'pending', 'approved_by': None, 'approved_at': None, 'rejection_reason': None } }, 'task_sessions': [], 'summary': { 'total_tasks': 0, 'tasks_completed': 0, 'entities_created': 0, 'entities_updated': 0, 'entities_deleted': 0, 'files_created': 0, 'files_updated': 0, 'files_deleted': 0 } } # Save session to version directory save_yaml(str(version_dir / 'session.yml'), session) # Update current state pointer get_workflow_dir().mkdir(exist_ok=True) save_yaml(str(get_current_state_path()), { 'active_version': version, 'session_id': session_id }) # Update index index = load_index() index['versions'].append({ 'version': version, 'feature': feature, 'status': 'pending', 'started_at': now.isoformat(), 'completed_at': None, 'tasks_count': 0, 'operations_count': 0 }) index['latest_version'] = version index['total_versions'] += 1 save_index(index) # Take snapshot of current state (manifest, tasks) take_snapshot(version, 'before') return session def load_current_session() -> Optional[dict]: """Load the current active workflow session.""" current_path = get_current_state_path() if not current_path.exists(): return None current = load_yaml(str(current_path)) version = current.get('active_version') if not version: return None session_path = get_version_dir(version) / 'session.yml' if not session_path.exists(): return None return load_yaml(str(session_path)) def save_current_session(session: dict): """Save the current workflow session.""" version = session['version'] session['updated_at'] = datetime.now().isoformat() save_yaml(str(get_version_dir(version) / 'session.yml'), session) # Update index index = load_index() for v in index['versions']: if v['version'] == version: v['status'] = session['status'] v['tasks_count'] = session['summary']['total_tasks'] break save_index(index) def complete_workflow_session(session: dict): """Mark workflow session as completed.""" now = datetime.now() session['status'] = 'completed' session['completed_at'] = now.isoformat() save_current_session(session) # Take final snapshot take_snapshot(session['version'], 'after') # Update index index = load_index() for v in index['versions']: if v['version'] == session['version']: v['status'] = 'completed' v['completed_at'] = now.isoformat() break save_index(index) # Clear current pointer current_path = get_current_state_path() if current_path.exists(): current_path.unlink() # ============================================================================ # Task Session Management # ============================================================================ def create_task_session(workflow_session: dict, task_id: str, task_type: str, agent: str) -> dict: """Create a new task session with full directory structure.""" now = datetime.now() session_id = f"tasksession_{task_id}_{now.strftime('%Y%m%d_%H%M%S')}" # Create task session DIRECTORY (not file) version_dir = get_version_dir(workflow_session['version']) task_session_dir = version_dir / 'task_sessions' / task_id task_session_dir.mkdir(parents=True, exist_ok=True) task_session = { 'session_id': session_id, 'workflow_version': workflow_session['version'], 'task_id': task_id, 'task_type': task_type, 'agent': agent, 'started_at': now.isoformat(), 'completed_at': None, 'duration_ms': None, 'status': 'in_progress', 'operations': [], 'review_session': None, 'errors': [], 'attempt_number': 1, 'previous_attempts': [] } # Save session.yml save_yaml(str(task_session_dir / 'session.yml'), task_session) # Snapshot task definition snapshot_task_definition(task_id, task_session_dir) # Initialize operations.log init_operations_log(task_session_dir, task_id, now) # Link to workflow workflow_session['task_sessions'].append(session_id) workflow_session['summary']['total_tasks'] += 1 save_current_session(workflow_session) return task_session def snapshot_task_definition(task_id: str, task_session_dir: Path): """Snapshot the task definition at execution time.""" 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', 'unknown') save_yaml(str(task_session_dir / 'task.yml'), task_data) def init_operations_log(task_session_dir: Path, task_id: str, start_time: datetime): """Initialize the operations log file.""" log_path = task_session_dir / 'operations.log' header = f"# Operations Log for {task_id}\n" header += f"# Started: {start_time.isoformat()}\n" header += "# Format: [timestamp] OPERATION target_type: target_id (path)\n" header += "=" * 70 + "\n\n" with open(log_path, 'w') as f: f.write(header) def log_to_task_operations_log(task_session: dict, operation: dict): """Append operation to task-specific operations log.""" version = task_session['workflow_version'] task_id = task_session['task_id'] log_path = get_version_dir(version) / 'task_sessions' / task_id / 'operations.log' if not log_path.exists(): return entry = ( f"[{operation['performed_at']}] " f"{operation['type']} {operation['target_type']}: {operation['target_id']}" ) if operation.get('target_path'): entry += f" ({operation['target_path']})" entry += f"\n Summary: {operation['changes']['diff_summary']}\n" with open(log_path, 'a') as f: f.write(entry + "\n") def load_task_session(version: str, task_id: str) -> Optional[dict]: """Load a task session from directory or flat file (backwards compatible).""" # Try new directory structure first session_dir = get_version_dir(version) / 'task_sessions' / task_id session_path = session_dir / 'session.yml' if session_path.exists(): return load_yaml(str(session_path)) # Fallback to old flat file structure old_path = get_version_dir(version) / 'task_sessions' / f'{task_id}.yml' if old_path.exists(): return load_yaml(str(old_path)) return None def save_task_session(task_session: dict): """Save a task session to directory structure.""" version = task_session['workflow_version'] task_id = task_session['task_id'] session_dir = get_version_dir(version) / 'task_sessions' / task_id session_dir.mkdir(parents=True, exist_ok=True) save_yaml(str(session_dir / 'session.yml'), task_session) def complete_task_session(task_session: dict, status: str = 'completed'): """Mark task session as completed.""" now = datetime.now() started = datetime.fromisoformat(task_session['started_at']) task_session['completed_at'] = now.isoformat() task_session['duration_ms'] = int((now - started).total_seconds() * 1000) task_session['status'] = status save_task_session(task_session) # Update workflow summary session = load_current_session() if session and status == 'completed': session['summary']['tasks_completed'] += 1 save_current_session(session) # ============================================================================ # Operation Logging # ============================================================================ def log_operation( task_session: dict, op_type: str, # CREATE, UPDATE, DELETE, RENAME, MOVE target_type: str, # file, entity, task, manifest target_id: str, target_path: str = None, before_state: str = None, after_state: str = None, diff_summary: str = None, rollback_data: dict = None ) -> dict: """Log an operation within a task session.""" now = datetime.now() seq = len(task_session['operations']) + 1 op_id = f"op_{now.strftime('%Y%m%d_%H%M%S')}_{seq:03d}" operation = { 'id': op_id, 'type': op_type, 'target_type': target_type, 'target_id': target_id, 'target_path': target_path, 'changes': { 'before': before_state, 'after': after_state, 'diff_summary': diff_summary or f"{op_type} {target_type}: {target_id}" }, 'performed_at': now.isoformat(), 'reversible': rollback_data is not None, 'rollback_data': rollback_data } task_session['operations'].append(operation) save_task_session(task_session) # Update workflow summary session = load_current_session() if session: if op_type == 'CREATE': if target_type == 'file': session['summary']['files_created'] += 1 elif target_type == 'entity': session['summary']['entities_created'] += 1 elif op_type == 'UPDATE': if target_type == 'file': session['summary']['files_updated'] += 1 elif target_type == 'entity': session['summary']['entities_updated'] += 1 elif op_type == 'DELETE': if target_type == 'file': session['summary']['files_deleted'] += 1 elif target_type == 'entity': session['summary']['entities_deleted'] += 1 save_current_session(session) # Also log to operations log log_to_file(operation, task_session) # Also log to task-specific operations log log_to_task_operations_log(task_session, operation) # Update index operations count index = load_index() for v in index['versions']: if v['version'] == task_session['workflow_version']: v['operations_count'] = v.get('operations_count', 0) + 1 break save_index(index) return operation def log_to_file(operation: dict, task_session: dict): """Append operation to global operations log.""" log_path = get_operations_log_path() log_entry = ( f"[{operation['performed_at']}] " f"v{task_session['workflow_version']} | " f"{task_session['task_id']} | " f"{operation['type']} {operation['target_type']}: {operation['target_id']}" ) if operation['target_path']: log_entry += f" ({operation['target_path']})" log_entry += "\n" with open(log_path, 'a') as f: f.write(log_entry) # ============================================================================ # Review Session Management # ============================================================================ def create_review_session(task_session: dict, reviewer: str = 'reviewer') -> dict: """Create a review session for a task.""" now = datetime.now() session_id = f"review_{task_session['task_id']}_{now.strftime('%Y%m%d_%H%M%S')}" review = { 'session_id': session_id, 'task_session_id': task_session['session_id'], 'workflow_version': task_session['workflow_version'], 'reviewer': reviewer, 'started_at': now.isoformat(), 'completed_at': None, 'decision': None, 'checks': { 'file_exists': None, 'manifest_compliance': None, 'code_quality': None, 'lint': None, 'build': None, 'tests': None }, 'notes': '', 'issues_found': [], 'suggestions': [] } task_session['review_session'] = review save_task_session(task_session) return review def complete_review_session( task_session: dict, decision: str, checks: dict, notes: str = '', issues: list = None, suggestions: list = None ): """Complete a review session.""" now = datetime.now() review = task_session['review_session'] review['completed_at'] = now.isoformat() review['decision'] = decision review['checks'].update(checks) review['notes'] = notes review['issues_found'] = issues or [] review['suggestions'] = suggestions or [] save_task_session(task_session) # ============================================================================ # Snapshots # ============================================================================ def take_snapshot(version: str, snapshot_type: str): """Take a snapshot of current state (before/after).""" snapshot_dir = get_version_dir(version) / f'snapshot_{snapshot_type}' snapshot_dir.mkdir(exist_ok=True) # Snapshot manifest if os.path.exists('project_manifest.json'): shutil.copy('project_manifest.json', snapshot_dir / 'manifest.json') # Snapshot tasks directory if os.path.exists('tasks'): tasks_snapshot = snapshot_dir / 'tasks' if tasks_snapshot.exists(): shutil.rmtree(tasks_snapshot) shutil.copytree('tasks', tasks_snapshot) # ============================================================================ # History & Diff # ============================================================================ def list_versions() -> list: """List all workflow versions.""" index = load_index() return index['versions'] def get_version_details(version: str) -> Optional[dict]: """Get detailed info about a version.""" session_path = get_version_dir(version) / 'session.yml' if not session_path.exists(): return None return load_yaml(str(session_path)) def get_changelog(version: str) -> dict: """Generate changelog for a version.""" session = get_version_details(version) if not session: return None changelog = { 'version': version, 'feature': session['feature'], 'status': session['status'], 'started_at': session['started_at'], 'completed_at': session['completed_at'], 'operations': { 'created': [], 'updated': [], 'deleted': [] }, 'summary': session['summary'] } # Collect operations from all task sessions tasks_dir = get_version_dir(version) / 'task_sessions' if tasks_dir.exists(): for task_file in tasks_dir.glob('*.yml'): task = load_yaml(str(task_file)) for op in task.get('operations', []): entry = { 'type': op['target_type'], 'id': op['target_id'], 'path': op['target_path'], 'task': task['task_id'], 'agent': task['agent'] } if op['type'] == 'CREATE': changelog['operations']['created'].append(entry) elif op['type'] == 'UPDATE': changelog['operations']['updated'].append(entry) elif op['type'] == 'DELETE': changelog['operations']['deleted'].append(entry) return changelog def diff_versions(version1: str, version2: str) -> dict: """Compare two versions.""" v1 = get_version_details(version1) v2 = get_version_details(version2) if not v1 or not v2: return None return { 'from_version': version1, 'to_version': version2, 'from_feature': v1['feature'], 'to_feature': v2['feature'], 'summary_diff': { 'entities_created': v2['summary']['entities_created'] - v1['summary']['entities_created'], 'entities_updated': v2['summary']['entities_updated'] - v1['summary']['entities_updated'], 'files_created': v2['summary']['files_created'] - v1['summary']['files_created'], 'files_updated': v2['summary']['files_updated'] - v1['summary']['files_updated'] } } # ============================================================================ # Display Functions # ============================================================================ def show_history(): """Display version history.""" versions = list_versions() print() print("╔" + "═" * 70 + "╗") print("║" + "WORKFLOW VERSION HISTORY".center(70) + "║") print("╠" + "═" * 70 + "╣") if not versions: print("║" + " No workflow versions found.".ljust(70) + "║") else: for v in versions: status_icon = "✅" if v['status'] == 'completed' else "🔄" if v['status'] == 'in_progress' else "⏳" line1 = f" {status_icon} {v['version']}: {v['feature'][:45]}" print("║" + line1.ljust(70) + "║") line2 = f" Started: {v['started_at'][:19]} | Tasks: {v['tasks_count']} | Ops: {v.get('operations_count', 0)}" print("║" + line2.ljust(70) + "║") print("║" + "─" * 70 + "║") print("╚" + "═" * 70 + "╝") def show_changelog(version: str): """Display changelog for a version.""" changelog = get_changelog(version) if not changelog: print(f"Version {version} not found.") return print() print("╔" + "═" * 70 + "╗") print("║" + f"CHANGELOG: {version}".center(70) + "║") print("╠" + "═" * 70 + "╣") print("║" + f" Feature: {changelog['feature'][:55]}".ljust(70) + "║") print("║" + f" Status: {changelog['status']}".ljust(70) + "║") print("╠" + "═" * 70 + "╣") ops = changelog['operations'] print("║" + " CREATED".ljust(70) + "║") for item in ops['created']: print("║" + f" + [{item['type']}] {item['id']}".ljust(70) + "║") if item['path']: print("║" + f" {item['path']}".ljust(70) + "║") print("║" + " UPDATED".ljust(70) + "║") for item in ops['updated']: print("║" + f" ~ [{item['type']}] {item['id']}".ljust(70) + "║") print("║" + " DELETED".ljust(70) + "║") for item in ops['deleted']: print("║" + f" - [{item['type']}] {item['id']}".ljust(70) + "║") print("╠" + "═" * 70 + "╣") s = changelog['summary'] print("║" + " SUMMARY".ljust(70) + "║") print("║" + f" Entities: +{s['entities_created']} ~{s['entities_updated']} -{s['entities_deleted']}".ljust(70) + "║") print("║" + f" Files: +{s['files_created']} ~{s['files_updated']} -{s['files_deleted']}".ljust(70) + "║") print("╚" + "═" * 70 + "╝") def show_current(): """Show current active workflow.""" session = load_current_session() if not session: print("No active workflow.") print("Start one with: /workflow:spawn 'feature name'") return print() print("╔" + "═" * 70 + "╗") print("║" + "CURRENT WORKFLOW SESSION".center(70) + "║") print("╠" + "═" * 70 + "╣") print("║" + f" Version: {session['version']}".ljust(70) + "║") print("║" + f" Feature: {session['feature'][:55]}".ljust(70) + "║") print("║" + f" Phase: {session['current_phase']}".ljust(70) + "║") print("║" + f" Status: {session['status']}".ljust(70) + "║") print("╠" + "═" * 70 + "╣") print("║" + " APPROVALS".ljust(70) + "║") d = session['approvals']['design'] i = session['approvals']['implementation'] d_icon = "✅" if d['status'] == 'approved' else "❌" if d['status'] == 'rejected' else "⏳" i_icon = "✅" if i['status'] == 'approved' else "❌" if i['status'] == 'rejected' else "⏳" print("║" + f" {d_icon} Design: {d['status']}".ljust(70) + "║") print("║" + f" {i_icon} Implementation: {i['status']}".ljust(70) + "║") print("╠" + "═" * 70 + "╣") s = session['summary'] print("║" + " PROGRESS".ljust(70) + "║") print("║" + f" Tasks: {s['tasks_completed']}/{s['total_tasks']} completed".ljust(70) + "║") print("║" + f" Entities: +{s['entities_created']} ~{s['entities_updated']} -{s['entities_deleted']}".ljust(70) + "║") print("║" + f" Files: +{s['files_created']} ~{s['files_updated']} -{s['files_deleted']}".ljust(70) + "║") print("╚" + "═" * 70 + "╝") # ============================================================================ # CLI Interface # ============================================================================ def main(): parser = argparse.ArgumentParser(description="Workflow versioning system") subparsers = parser.add_subparsers(dest='command', help='Commands') # create command create_parser = subparsers.add_parser('create', help='Create new workflow version') create_parser.add_argument('feature', help='Feature description') create_parser.add_argument('--parent', help='Parent version (for fixes)') # current command subparsers.add_parser('current', help='Show current workflow') # history command subparsers.add_parser('history', help='Show version history') # changelog command changelog_parser = subparsers.add_parser('changelog', help='Show version changelog') changelog_parser.add_argument('version', help='Version to show') # diff command diff_parser = subparsers.add_parser('diff', help='Compare two versions') diff_parser.add_argument('version1', help='First version') diff_parser.add_argument('version2', help='Second version') # task-start command task_start = subparsers.add_parser('task-start', help='Start a task session') task_start.add_argument('task_id', help='Task ID') task_start.add_argument('--type', default='create', help='Task type') task_start.add_argument('--agent', required=True, help='Agent performing task') # task-complete command task_complete = subparsers.add_parser('task-complete', help='Complete a task session') task_complete.add_argument('task_id', help='Task ID') task_complete.add_argument('--status', default='completed', help='Final status') # log-op command log_op = subparsers.add_parser('log-op', help='Log an operation') log_op.add_argument('task_id', help='Task ID') log_op.add_argument('op_type', choices=['CREATE', 'UPDATE', 'DELETE']) log_op.add_argument('target_type', choices=['file', 'entity', 'task', 'manifest']) log_op.add_argument('target_id', help='Target ID') log_op.add_argument('--path', help='File path if applicable') log_op.add_argument('--summary', help='Change summary') # complete command subparsers.add_parser('complete', help='Complete current workflow') # update-phase command phase_parser = subparsers.add_parser('update-phase', help='Update workflow phase') phase_parser.add_argument('phase', help='New phase') # tasks-dir command tasks_dir_parser = subparsers.add_parser('tasks-dir', help='Get tasks directory for current or specific version') tasks_dir_parser.add_argument('--version', help='Specific version (defaults to current)') args = parser.parse_args() if args.command == 'create': session = create_workflow_session(args.feature, args.parent) print(f"Created workflow version: {session['version']}") print(f"Feature: {args.feature}") print(f"Session ID: {session['session_id']}") elif args.command == 'current': show_current() elif args.command == 'history': show_history() elif args.command == 'changelog': show_changelog(args.version) elif args.command == 'diff': result = diff_versions(args.version1, args.version2) if result: print(json.dumps(result, indent=2)) else: print("Could not compare versions") elif args.command == 'task-start': session = load_current_session() if not session: print("Error: No active workflow") sys.exit(1) task = create_task_session(session, args.task_id, args.type, args.agent) print(f"Started task session: {task['session_id']}") elif args.command == 'task-complete': session = load_current_session() if not session: print("Error: No active workflow") sys.exit(1) task = load_task_session(session['version'], args.task_id) if task: complete_task_session(task, args.status) print(f"Completed task: {args.task_id}") else: print(f"Task session not found: {args.task_id}") elif args.command == 'log-op': session = load_current_session() if not session: print("Error: No active workflow") sys.exit(1) task = load_task_session(session['version'], args.task_id) if task: op = log_operation( task, args.op_type, args.target_type, args.target_id, target_path=args.path, diff_summary=args.summary ) print(f"Logged operation: {op['id']}") else: print(f"Task session not found: {args.task_id}") elif args.command == 'complete': session = load_current_session() if not session: print("Error: No active workflow") sys.exit(1) complete_workflow_session(session) print(f"Completed workflow: {session['version']}") elif args.command == 'update-phase': session = load_current_session() if not session: print("Error: No active workflow") sys.exit(1) session['current_phase'] = args.phase save_current_session(session) print(f"Updated phase to: {args.phase}") elif args.command == 'tasks-dir': if args.version: # Specific version requested tasks_dir = get_version_tasks_dir(args.version) tasks_dir.mkdir(parents=True, exist_ok=True) print(str(tasks_dir)) else: # Use current version tasks_dir = get_current_tasks_dir() if tasks_dir: print(str(tasks_dir)) else: print("Error: No active workflow") sys.exit(1) else: parser.print_help() if __name__ == "__main__": main()