## Database Command Reorganization
- Add new db-prefixed commands: db-query, db-schema, db-delete, db-status
- Maintain backward compatibility with deprecation warnings for query/schema commands
- Implement lazy database initialization to reduce CLI coupling
- Add command-specific --database options for flexibility
## Legacy Compatibility Framework
- Create comprehensive legacy compatibility system in markitect/legacy_compat.py
- Support versioned legacy switches (--legacy-v39-pre) for smooth transitions
- Implement git commit binding for version tracking (Issue #39: v39-pre → 3168de4)
- Add environment-based legacy mode detection for test environments
- Create graduated deprecation warning system (DEPRECATED → LEGACY → SUNSET)
## Legacy Agent System
- Implement intelligent legacy lifecycle management agent
- Add 8 CLI commands for legacy interface management (status, analyze, migrate, cleanup, etc.)
- Create automated maintenance with usage analytics and data-driven decisions
- Provide comprehensive safety features with backup and rollback capabilities
## Test Architecture Enhancement
- Add 18 comprehensive tests for Issue #39 functionality (16 passing, 2 skipped by design)
- Configure pytest.ini with MARKITECT_LEGACY_MODE=39-pre for automatic legacy support
- Update test count to 466 total tests across 7 architectural layers
- Identify 5 legacy interface tests for future recreation without legacy dependencies
## Documentation & Roadmap Updates
- Update NEXT.md with completed Issues #39 and #40
- Document failing tests requiring recreation with pure db- commands
- Add comprehensive legacy agent documentation
- Update development priorities and capability descriptions
## Architecture Achievements
- Simplified CLI architecture with reduced coupling between commands and global state
- Created reusable legacy compatibility framework for future breaking changes
- Established systematic approach to interface deprecation and migration
- Maintained 461/466 tests passing (5 legacy interface tests flagged for recreation)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
330 lines
11 KiB
Python
330 lines
11 KiB
Python
"""
|
|
Legacy Switch System - CLI switches for legacy version control.
|
|
|
|
Provides decorators and utilities for adding legacy switches to CLI commands.
|
|
Supports --legacy-v1, --legacy-v2 style switches with automatic version detection
|
|
and deprecation warnings.
|
|
"""
|
|
|
|
import click
|
|
import functools
|
|
from typing import Optional, Callable, Any, Dict, List
|
|
from dataclasses import dataclass
|
|
|
|
from .registry import LegacyRegistry, LegacyStatus
|
|
from .deprecation import DeprecationManager, DeprecationLevel
|
|
from .exceptions import LegacyVersionNotFoundError
|
|
|
|
|
|
@dataclass
|
|
class LegacySwitch:
|
|
"""Configuration for a legacy CLI switch."""
|
|
version: str
|
|
flag_name: str
|
|
help_text: str
|
|
deprecation_level: DeprecationLevel = DeprecationLevel.WARNING
|
|
hidden: bool = False
|
|
|
|
|
|
class LegacySwitchManager:
|
|
"""Manages legacy switches for CLI commands."""
|
|
|
|
def __init__(self):
|
|
self.registry = LegacyRegistry()
|
|
self.deprecation_manager = DeprecationManager()
|
|
|
|
def create_switch_options(self, command: str) -> List[LegacySwitch]:
|
|
"""
|
|
Create legacy switch options for a command.
|
|
|
|
Args:
|
|
command: Command name
|
|
|
|
Returns:
|
|
List of LegacySwitch configurations
|
|
"""
|
|
switches = []
|
|
legacy_versions = self.registry.get_legacy_versions(command)
|
|
|
|
for interface in legacy_versions:
|
|
if interface.status == LegacyStatus.REMOVED:
|
|
continue
|
|
|
|
# Determine deprecation level based on status
|
|
if interface.status == LegacyStatus.SUNSET:
|
|
level = DeprecationLevel.CRITICAL
|
|
elif interface.status == LegacyStatus.LEGACY:
|
|
level = DeprecationLevel.WARNING
|
|
else:
|
|
level = DeprecationLevel.INFO
|
|
|
|
# Create flag name
|
|
flag_name = f"legacy-{interface.version}"
|
|
if flag_name.startswith("legacy-v"):
|
|
# Already has v prefix
|
|
pass
|
|
elif interface.version.startswith("v"):
|
|
flag_name = f"legacy-{interface.version}"
|
|
else:
|
|
flag_name = f"legacy-v{interface.version}"
|
|
|
|
help_text = f"Use {interface.version} legacy behavior"
|
|
if interface.description:
|
|
help_text += f" - {interface.description}"
|
|
|
|
switches.append(LegacySwitch(
|
|
version=interface.version,
|
|
flag_name=flag_name,
|
|
help_text=help_text,
|
|
deprecation_level=level,
|
|
hidden=(interface.status == LegacyStatus.SUNSET)
|
|
))
|
|
|
|
return switches
|
|
|
|
def execute_with_legacy_support(
|
|
self,
|
|
command: str,
|
|
modern_implementation: Callable,
|
|
legacy_options: Dict[str, bool],
|
|
*args,
|
|
**kwargs
|
|
) -> Any:
|
|
"""
|
|
Execute a command with legacy support.
|
|
|
|
Args:
|
|
command: Command name
|
|
modern_implementation: Modern implementation function
|
|
legacy_options: Dictionary of legacy option flags
|
|
*args, **kwargs: Arguments for the implementation
|
|
|
|
Returns:
|
|
Result of the appropriate implementation
|
|
"""
|
|
# Find which legacy option is enabled
|
|
active_legacy = None
|
|
for option_name, is_enabled in legacy_options.items():
|
|
if is_enabled:
|
|
if active_legacy:
|
|
raise click.ClickException(
|
|
"Cannot specify multiple legacy options simultaneously"
|
|
)
|
|
# Extract version from option name (e.g., legacy_v1_0 -> v1.0)
|
|
version = self._extract_version_from_option(option_name)
|
|
active_legacy = version
|
|
|
|
if not active_legacy:
|
|
# Use modern implementation
|
|
return modern_implementation(*args, **kwargs)
|
|
|
|
# Use legacy implementation
|
|
try:
|
|
interface = self.registry.get_legacy_interface(command, active_legacy)
|
|
if not interface:
|
|
available = self.registry.get_available_versions(command)
|
|
raise LegacyVersionNotFoundError(command, active_legacy, available)
|
|
|
|
# Show deprecation warning
|
|
self.deprecation_manager.warn_deprecated_usage(
|
|
command, active_legacy, interface.status.value,
|
|
interface.removal_date, interface.migration_guide
|
|
)
|
|
|
|
# Execute legacy implementation
|
|
return self.registry.execute_legacy(command, active_legacy, *args, **kwargs)
|
|
|
|
except LegacyVersionNotFoundError:
|
|
# Fallback to modern implementation with warning
|
|
click.echo(
|
|
f"Warning: Legacy version {active_legacy} not available for {command}. "
|
|
f"Using current implementation.", err=True
|
|
)
|
|
return modern_implementation(*args, **kwargs)
|
|
|
|
def _extract_version_from_option(self, option_name: str) -> str:
|
|
"""Extract version identifier from option name."""
|
|
# Convert legacy_v1_0 -> v1.0, legacy_v2 -> v2, etc.
|
|
if option_name.startswith("legacy_v"):
|
|
version_part = option_name[8:] # Remove "legacy_v"
|
|
return "v" + version_part.replace("_", ".")
|
|
elif option_name.startswith("legacy_"):
|
|
return option_name[7:] # Remove "legacy_"
|
|
else:
|
|
return option_name
|
|
|
|
|
|
def legacy_option(version: str, help_text: str = None) -> Callable:
|
|
"""
|
|
Decorator to add a legacy option to a CLI command.
|
|
|
|
Args:
|
|
version: Legacy version identifier (e.g., "v1.0", "v2")
|
|
help_text: Custom help text for the option
|
|
|
|
Returns:
|
|
Click option decorator
|
|
|
|
Example:
|
|
@legacy_option("v1.0", "Use v1.0 legacy query behavior")
|
|
@click.command()
|
|
def my_command(legacy_v1_0):
|
|
if legacy_v1_0:
|
|
# Use legacy implementation
|
|
pass
|
|
"""
|
|
# Create flag name
|
|
flag_name = f"legacy-{version}"
|
|
if not flag_name.startswith("legacy-v") and not version.startswith("v"):
|
|
flag_name = f"legacy-v{version}"
|
|
|
|
# Create option name (replace hyphens with underscores)
|
|
option_name = flag_name.replace("-", "_")
|
|
|
|
# Default help text
|
|
if not help_text:
|
|
help_text = f"Use {version} legacy behavior (deprecated)"
|
|
|
|
return click.option(
|
|
f'--{flag_name}',
|
|
option_name,
|
|
is_flag=True,
|
|
help=help_text
|
|
)
|
|
|
|
|
|
def legacy_command(command_name: str, auto_switches: bool = True):
|
|
"""
|
|
Decorator to add automatic legacy support to a CLI command.
|
|
|
|
Args:
|
|
command_name: Name of the command for legacy registry lookup
|
|
auto_switches: Whether to automatically add legacy switches
|
|
|
|
Returns:
|
|
Decorator function
|
|
|
|
Example:
|
|
@legacy_command("query")
|
|
@click.command()
|
|
def query_command(**kwargs):
|
|
# Implementation with automatic legacy support
|
|
pass
|
|
"""
|
|
def decorator(func: Callable) -> Callable:
|
|
if auto_switches:
|
|
# Add legacy switches automatically
|
|
switch_manager = LegacySwitchManager()
|
|
switches = switch_manager.create_switch_options(command_name)
|
|
|
|
# Apply switches in reverse order (click applies decorators bottom-up)
|
|
for switch in reversed(switches):
|
|
option_name = switch.flag_name.replace("-", "_")
|
|
func = click.option(
|
|
f'--{switch.flag_name}',
|
|
option_name,
|
|
is_flag=True,
|
|
help=switch.help_text,
|
|
hidden=switch.hidden
|
|
)(func)
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
switch_manager = LegacySwitchManager()
|
|
|
|
# Extract legacy options from kwargs
|
|
legacy_options = {}
|
|
func_kwargs = {}
|
|
|
|
for key, value in kwargs.items():
|
|
if key.startswith("legacy_"):
|
|
legacy_options[key] = value
|
|
else:
|
|
func_kwargs[key] = value
|
|
|
|
# Define modern implementation
|
|
def modern_impl(*impl_args, **impl_kwargs):
|
|
return func(*impl_args, **impl_kwargs)
|
|
|
|
# Execute with legacy support
|
|
return switch_manager.execute_with_legacy_support(
|
|
command_name, modern_impl, legacy_options, *args, **func_kwargs
|
|
)
|
|
|
|
return wrapper
|
|
return decorator
|
|
|
|
|
|
def create_legacy_switches_for_command(command: str) -> List[Callable]:
|
|
"""
|
|
Create a list of legacy option decorators for a command.
|
|
|
|
Args:
|
|
command: Command name
|
|
|
|
Returns:
|
|
List of click option decorators
|
|
|
|
Example:
|
|
decorators = create_legacy_switches_for_command("query")
|
|
for decorator in decorators:
|
|
my_command = decorator(my_command)
|
|
"""
|
|
switch_manager = LegacySwitchManager()
|
|
switches = switch_manager.create_switch_options(command)
|
|
|
|
decorators = []
|
|
for switch in switches:
|
|
option_name = switch.flag_name.replace("-", "_")
|
|
decorator = click.option(
|
|
f'--{switch.flag_name}',
|
|
option_name,
|
|
is_flag=True,
|
|
help=switch.help_text,
|
|
hidden=switch.hidden
|
|
)
|
|
decorators.append(decorator)
|
|
|
|
return decorators
|
|
|
|
|
|
def with_legacy_support(command_name: str):
|
|
"""
|
|
Simplified decorator for adding legacy support to existing commands.
|
|
|
|
Args:
|
|
command_name: Name of the command for legacy registry lookup
|
|
|
|
Returns:
|
|
Decorator function
|
|
|
|
Example:
|
|
@with_legacy_support("query")
|
|
def my_existing_function(*args, **kwargs):
|
|
# Your existing implementation
|
|
pass
|
|
"""
|
|
def decorator(func: Callable) -> Callable:
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
# Check if any legacy options are present in kwargs
|
|
legacy_options = {k: v for k, v in kwargs.items() if k.startswith("legacy_") and v}
|
|
|
|
if not legacy_options:
|
|
# No legacy options, use modern implementation
|
|
return func(*args, **kwargs)
|
|
|
|
# Legacy option detected, use legacy manager
|
|
switch_manager = LegacySwitchManager()
|
|
|
|
def modern_impl(*impl_args, **impl_kwargs):
|
|
# Remove legacy options from kwargs before calling modern implementation
|
|
clean_kwargs = {k: v for k, v in impl_kwargs.items() if not k.startswith("legacy_")}
|
|
return func(*impl_args, **clean_kwargs)
|
|
|
|
return switch_manager.execute_with_legacy_support(
|
|
command_name, modern_impl, legacy_options, *args, **kwargs
|
|
)
|
|
|
|
return wrapper
|
|
return decorator |