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

987 lines
33 KiB
Python
Executable File

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