TradingAgents/.claude/lib/brownfield_retrofit.py

500 lines
16 KiB
Python

#!/usr/bin/env python3
"""
Brownfield Retrofit - Core state management and phase coordination
This module provides the main coordinator for brownfield project adoption:
- 5-phase retrofit workflow (Analyze → Assess → Plan → Execute → Verify)
- State persistence and recovery
- Phase prerequisite validation
- Secure state file management (0o600 permissions)
- Backup and rollback support
Phases:
1. ANALYZE: Tech stack detection, file organization analysis
2. ASSESS: PROJECT.md generation, 12-Factor scoring, gap identification
3. PLAN: Migration step generation, effort estimation
4. EXECUTE: Safe file modifications with backup/rollback
5. VERIFY: Compliance validation, test suite execution
Security:
- State file permissions: 0o600 (user-only)
- All paths validated via security_utils.validate_path()
- Audit logging for all operations
Relevant Skills:
- project-alignment-validation: Alignment checklist for retrofit validation
Usage:
from brownfield_retrofit import BrownfieldRetrofit, RetrofitPhase
# Initialize
retrofit = BrownfieldRetrofit(project_root="/path/to/project")
# Check status
status = retrofit.get_phase_status()
# Execute phase
retrofit.execute_phase(RetrofitPhase.ANALYZE)
Date: 2025-11-11
Feature: /align-project-retrofit command
Agent: implementer
Design Patterns:
See library-design-patterns skill for standardized design patterns.
See api-integration-patterns skill for standardized design patterns.
"""
import json
import shutil
import sys
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional
# Add parent directory for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from plugins.autonomous_dev.lib import security_utils
# Exception hierarchy pattern from error-handling-patterns skill:
# BaseException -> Exception -> AutonomousDevError -> DomainError(BaseException) -> SpecificError
class StateError(Exception):
"""
See error-handling-patterns skill for exception hierarchy and error handling best practices.
Exception raised for state management errors."""
pass
class RetrofitPhase(Enum):
"""Retrofit workflow phases."""
NOT_STARTED = "NOT_STARTED"
ANALYZE = "ANALYZE"
ASSESS = "ASSESS"
PLAN = "PLAN"
EXECUTE = "EXECUTE"
VERIFY = "VERIFY"
COMPLETE = "COMPLETE"
def __str__(self) -> str:
return self.value
@classmethod
def from_string(cls, value: str) -> "RetrofitPhase":
"""Convert string to RetrofitPhase enum."""
try:
return cls[value]
except KeyError:
raise ValueError(f"Invalid phase: {value}")
# Phase dependency chain
PHASE_ORDER = [
RetrofitPhase.NOT_STARTED,
RetrofitPhase.ANALYZE,
RetrofitPhase.ASSESS,
RetrofitPhase.PLAN,
RetrofitPhase.EXECUTE,
RetrofitPhase.VERIFY,
RetrofitPhase.COMPLETE,
]
# Phase prerequisites
PHASE_PREREQUISITES: Dict[RetrofitPhase, List[RetrofitPhase]] = {
RetrofitPhase.NOT_STARTED: [],
RetrofitPhase.ANALYZE: [],
RetrofitPhase.ASSESS: [RetrofitPhase.ANALYZE],
RetrofitPhase.PLAN: [RetrofitPhase.ANALYZE, RetrofitPhase.ASSESS],
RetrofitPhase.EXECUTE: [RetrofitPhase.ANALYZE, RetrofitPhase.ASSESS, RetrofitPhase.PLAN],
RetrofitPhase.VERIFY: [
RetrofitPhase.ANALYZE,
RetrofitPhase.ASSESS,
RetrofitPhase.PLAN,
RetrofitPhase.EXECUTE,
],
RetrofitPhase.COMPLETE: [
RetrofitPhase.ANALYZE,
RetrofitPhase.ASSESS,
RetrofitPhase.PLAN,
RetrofitPhase.EXECUTE,
RetrofitPhase.VERIFY,
],
}
@dataclass
class RetrofitState:
"""State container for retrofit workflow.
Attributes:
current_phase: Current workflow phase
completed_phases: List of completed phases
analysis_report: Phase 1 analysis results
assessment_report: Phase 2 assessment results
migration_plan: Phase 3 migration plan
execution_results: Phase 4 execution results
verification_report: Phase 5 verification results
backup_path: Path to backup directory (if created)
metadata: Additional metadata (timestamps, etc.)
"""
current_phase: RetrofitPhase = RetrofitPhase.NOT_STARTED
completed_phases: List[RetrofitPhase] = field(default_factory=list)
analysis_report: Optional[Dict[str, Any]] = None
assessment_report: Optional[Dict[str, Any]] = None
migration_plan: Optional[Dict[str, Any]] = None
execution_results: Optional[Dict[str, Any]] = None
verification_report: Optional[Dict[str, Any]] = None
backup_path: Optional[Path] = None
metadata: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
"""Initialize metadata with timestamps."""
if "created_at" not in self.metadata:
self.metadata["created_at"] = datetime.now().isoformat()
self.metadata["updated_at"] = datetime.now().isoformat()
def to_dict(self) -> Dict[str, Any]:
"""Serialize state to dictionary.
Returns:
Dictionary representation of state
"""
return {
"current_phase": self.current_phase.value,
"completed_phases": [phase.value for phase in self.completed_phases],
"analysis_report": self.analysis_report,
"assessment_report": self.assessment_report,
"migration_plan": self.migration_plan,
"execution_results": self.execution_results,
"verification_report": self.verification_report,
"backup_path": str(self.backup_path) if self.backup_path else None,
"metadata": self.metadata,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RetrofitState":
"""Deserialize state from dictionary.
Args:
data: Dictionary representation of state
Returns:
RetrofitState instance
"""
return cls(
current_phase=RetrofitPhase.from_string(data["current_phase"]),
completed_phases=[
RetrofitPhase.from_string(phase) for phase in data.get("completed_phases", [])
],
analysis_report=data.get("analysis_report"),
assessment_report=data.get("assessment_report"),
migration_plan=data.get("migration_plan"),
execution_results=data.get("execution_results"),
verification_report=data.get("verification_report"),
backup_path=Path(data["backup_path"]) if data.get("backup_path") else None,
metadata=data.get("metadata", {}),
)
def mark_phase_complete(self, phase: RetrofitPhase) -> None:
"""Mark phase as complete and advance to next phase.
Args:
phase: Phase to mark as complete
"""
if phase not in self.completed_phases:
self.completed_phases.append(phase)
# Advance to next phase (use phase parameter, not current_phase)
phase_index = PHASE_ORDER.index(phase)
if phase_index < len(PHASE_ORDER) - 1:
self.current_phase = PHASE_ORDER[phase_index + 1]
self.metadata["updated_at"] = datetime.now().isoformat()
def can_execute_phase(self, phase: RetrofitPhase) -> bool:
"""Check if phase can be executed (prerequisites met).
Args:
phase: Phase to check
Returns:
True if prerequisites met, False otherwise
"""
prerequisites = PHASE_PREREQUISITES.get(phase, [])
return all(prereq in self.completed_phases for prereq in prerequisites)
class BrownfieldRetrofit:
"""Main coordinator for brownfield project retrofit workflow.
This class manages the 5-phase retrofit workflow:
1. ANALYZE: Tech stack detection and metrics
2. ASSESS: PROJECT.md generation and gap analysis
3. PLAN: Migration step generation and estimation
4. EXECUTE: Safe file modifications with backup
5. VERIFY: Compliance validation and testing
Attributes:
project_root: Path to project root directory
state: Current workflow state
state_dir: Path to .retrofit directory
state_file: Path to state.json file
"""
STATE_DIR = ".retrofit"
STATE_FILE = "state.json"
STATE_PERMISSIONS = 0o600 # User read/write only
def __init__(self, project_root: Path):
"""Initialize retrofit coordinator.
Args:
project_root: Path to project root directory
Raises:
ValueError: If project_root validation fails
"""
# Validate project root
security_utils.validate_path(
str(project_root),
purpose="brownfield retrofit project root",
allow_missing=False,
)
self.project_root = Path(project_root).resolve()
# Initialize state directory
self.state_dir = self.project_root / self.STATE_DIR
self.state_file = self.state_dir / self.STATE_FILE
# Create state directory if missing
self._ensure_state_directory()
# Initialize state
self.state = self.load_state()
# Audit log
security_utils.audit_log(
"brownfield_retrofit_init",
"success",
{
"project_root": str(self.project_root),
"state_dir": str(self.state_dir),
},
)
def _ensure_state_directory(self) -> None:
"""Create state directory if it doesn't exist.
Raises:
StateError: If directory creation fails
"""
try:
self.state_dir.mkdir(parents=True, exist_ok=True)
except PermissionError as e:
raise StateError(f"Permission denied: Cannot create state directory: {e}")
except Exception as e:
raise StateError(f"Failed to create state directory: {e}")
def load_state(self) -> RetrofitState:
"""Load state from state file or create new state.
Returns:
RetrofitState instance
Raises:
StateError: If state loading fails
"""
if not self.state_file.exists():
# Create new state
return RetrofitState()
try:
# Read state file
state_data = json.loads(self.state_file.read_text())
state = RetrofitState.from_dict(state_data)
security_utils.audit_log(
"brownfield_state_loaded",
"success",
{
"state_file": str(self.state_file),
"current_phase": state.current_phase.value,
},
)
return state
except json.JSONDecodeError as e:
# Corrupted state file - create backup and return new state
backup_file = self.state_file.with_suffix(".json.backup")
shutil.copy2(self.state_file, backup_file)
security_utils.audit_log(
"brownfield_state_corrupted",
"warning",
{
"state_file": str(self.state_file),
"backup_file": str(backup_file),
"error": str(e),
},
)
return RetrofitState()
except Exception as e:
raise StateError(f"Failed to load state: {e}")
def save_state(self) -> None:
"""Save current state to state file.
Raises:
StateError: If state saving fails
"""
try:
# Update timestamp
self.state.metadata["updated_at"] = datetime.now().isoformat()
# Serialize state
state_data = self.state.to_dict()
state_json = json.dumps(state_data, indent=2)
# Write to state file
self.state_file.write_text(state_json)
# Set secure permissions
self.state_file.chmod(self.STATE_PERMISSIONS)
security_utils.audit_log(
"brownfield_state_saved",
"success",
{
"state_file": str(self.state_file),
"current_phase": self.state.current_phase.value,
},
)
except PermissionError as e:
raise StateError(f"Permission denied: Cannot save state file: {e}")
except Exception as e:
raise StateError(f"Failed to save state: {e}")
def update_state(self, **kwargs) -> None:
"""Update state attributes and save automatically.
Args:
**kwargs: State attributes to update
Raises:
StateError: If state update fails
"""
for key, value in kwargs.items():
if hasattr(self.state, key):
# Handle phase enum conversion
if key == "current_phase" and isinstance(value, str):
value = RetrofitPhase.from_string(value)
elif key == "completed_phases" and value and isinstance(value[0], str):
value = [RetrofitPhase.from_string(p) for p in value]
setattr(self.state, key, value)
self.save_state()
def get_phase_status(self) -> Dict[str, Any]:
"""Get current phase status.
Returns:
Dictionary with phase status information
"""
completed_phase_values = [phase.value for phase in self.state.completed_phases]
current_index = PHASE_ORDER.index(self.state.current_phase)
# Remaining phases excludes NOT_STARTED and COMPLETE
remaining_phases = [
phase.value
for phase in PHASE_ORDER[current_index + 1 :]
if phase not in (RetrofitPhase.NOT_STARTED, RetrofitPhase.COMPLETE)
]
return {
"current_phase": self.state.current_phase.value,
"completed_phases": completed_phase_values,
"remaining_phases": remaining_phases,
"total_phases": len(PHASE_ORDER) - 2, # Exclude NOT_STARTED and COMPLETE
"progress_percent": len(completed_phase_values) / (len(PHASE_ORDER) - 2) * 100,
}
def execute_phase(self, phase: RetrofitPhase) -> None:
"""Execute a specific phase.
Args:
phase: Phase to execute
Raises:
StateError: If prerequisites not met
"""
if not self.state.can_execute_phase(phase):
raise StateError(
f"Prerequisites not met for phase {phase.value}. "
f"Required phases: {[p.value for p in PHASE_PREREQUISITES[phase]]}"
)
security_utils.audit_log(
"brownfield_phase_execute",
"success",
{
"phase": phase.value,
"project_root": str(self.project_root),
},
)
def complete_phase(self, phase: RetrofitPhase) -> None:
"""Mark phase as complete and advance workflow.
Args:
phase: Phase to mark as complete
"""
self.state.mark_phase_complete(phase)
self.save_state()
security_utils.audit_log(
"brownfield_phase_complete",
"success",
{
"phase": phase.value,
"next_phase": self.state.current_phase.value,
},
)
def reset(self) -> None:
"""Reset workflow to initial state.
This clears all state and starts from scratch.
"""
self.state = RetrofitState()
self.save_state()
security_utils.audit_log(
"brownfield_workflow_reset",
"success",
{"project_root": str(self.project_root)},
)
# Convenience function for external use
def create_retrofit_instance(project_root: Path) -> BrownfieldRetrofit:
"""Create a BrownfieldRetrofit instance.
Args:
project_root: Path to project root directory
Returns:
BrownfieldRetrofit instance
"""
return BrownfieldRetrofit(project_root=project_root)