- Add core agent architecture with Command + Skill pattern - Implement Claude API integration for content analysis - Add Obsidian REST API integration for vault operations - Create conversational interface (v2.0) with natural language processing - Add comprehensive configuration management and validation - Include project documentation and developer guides - Set up testing framework with unit, integration, and property tests - Add Kiro specs for Claude API configuration and code quality improvements - Configure project steering files for development guidelines
349 lines
14 KiB
Python
349 lines
14 KiB
Python
"""
|
|
Configuration migration utility for backward compatibility
|
|
Handles migration from legacy configuration formats to new enhanced format
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from pathlib import Path
|
|
|
|
|
|
class ConfigurationMigrator:
|
|
"""Handle migration from legacy configuration formats"""
|
|
|
|
def __init__(self):
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
# Model name migrations mapping old names to new names
|
|
self.model_migrations = {
|
|
# Legacy model names to current format
|
|
"claude-3-sonnet": "claude-3-sonnet-20240229",
|
|
"claude-3-opus": "claude-3-opus-20240229",
|
|
"claude-3-haiku": "claude-3-haiku-20240307",
|
|
"claude-3.5-sonnet": "claude-3-5-sonnet-20241022",
|
|
"claude-3.5-haiku": "claude-3-5-haiku-20241022",
|
|
# Handle common variations
|
|
"claude-sonnet": "claude-3-sonnet-20240229",
|
|
"claude-opus": "claude-3-opus-20240229",
|
|
"claude-haiku": "claude-3-haiku-20240307",
|
|
"sonnet": "claude-3-5-sonnet-20241022",
|
|
"opus": "claude-3-opus-20240229",
|
|
"haiku": "claude-3-haiku-20240307",
|
|
}
|
|
|
|
# Default values for new fields
|
|
self.default_values = {
|
|
'claude': {
|
|
'api_url': "https://api.anthropic.com",
|
|
'model': "claude-3-5-sonnet-20241022",
|
|
'max_tokens': 4096,
|
|
'temperature': 0.7
|
|
},
|
|
'journal': {
|
|
'daily_notes_folder': "Daily",
|
|
'date_format': "YYYY-MM-DD",
|
|
'file_extension': ".md"
|
|
},
|
|
'output': {
|
|
'experiences_folder': "Knowledge/Experiences",
|
|
'lessons_folder': "Knowledge/Lessons",
|
|
'tasks_folder': "Tasks/Daily",
|
|
'problems_folder': "Knowledge/Problems",
|
|
'achievements_folder': "Knowledge/Achievements",
|
|
'improvements_folder': "Knowledge/Improvements"
|
|
},
|
|
'analysis': {
|
|
'categories': [],
|
|
'extraction_rules': {}
|
|
},
|
|
'logging': {
|
|
'level': "INFO",
|
|
'file': "logs/journal_organizer.log"
|
|
}
|
|
}
|
|
|
|
def migrate_claude_config(self, config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Migrate legacy Claude configuration to new format
|
|
|
|
Args:
|
|
config_dict: Configuration dictionary to migrate
|
|
|
|
Returns:
|
|
Migrated configuration dictionary
|
|
"""
|
|
# Create a copy to avoid modifying the original
|
|
migrated_config = config_dict.copy()
|
|
|
|
# Ensure claude section exists
|
|
if 'claude' not in migrated_config:
|
|
migrated_config['claude'] = {}
|
|
self.logger.info("Created missing 'claude' configuration section")
|
|
|
|
claude_config = migrated_config['claude']
|
|
migration_actions = []
|
|
|
|
# Add default api_url if not present
|
|
if 'api_url' not in claude_config:
|
|
claude_config['api_url'] = self.default_values['claude']['api_url']
|
|
migration_actions.append(f"Added default api_url: {claude_config['api_url']}")
|
|
|
|
# Ensure model has a default value
|
|
if 'model' not in claude_config:
|
|
claude_config['model'] = self.default_values['claude']['model']
|
|
migration_actions.append(f"Added default model: {claude_config['model']}")
|
|
else:
|
|
# Migrate old model names to new format if needed
|
|
old_model = claude_config['model']
|
|
if old_model in self.model_migrations:
|
|
new_model = self.model_migrations[old_model]
|
|
claude_config['model'] = new_model
|
|
migration_actions.append(f"Migrated model '{old_model}' to '{new_model}'")
|
|
|
|
# Add other default values if missing
|
|
if 'max_tokens' not in claude_config:
|
|
claude_config['max_tokens'] = self.default_values['claude']['max_tokens']
|
|
migration_actions.append(f"Added default max_tokens: {claude_config['max_tokens']}")
|
|
|
|
if 'temperature' not in claude_config:
|
|
claude_config['temperature'] = self.default_values['claude']['temperature']
|
|
migration_actions.append(f"Added default temperature: {claude_config['temperature']}")
|
|
|
|
# Log migration actions
|
|
if migration_actions:
|
|
self.logger.info("Claude configuration migration completed:")
|
|
for action in migration_actions:
|
|
self.logger.info(f" - {action}")
|
|
|
|
return migrated_config
|
|
|
|
def migrate_configuration(self, config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Migrate complete configuration from legacy format to new format
|
|
|
|
Args:
|
|
config_dict: Configuration dictionary to migrate
|
|
|
|
Returns:
|
|
Migrated configuration dictionary
|
|
"""
|
|
migrated_config = config_dict.copy()
|
|
all_migration_actions = []
|
|
|
|
# Migrate Claude configuration
|
|
migrated_config = self.migrate_claude_config(migrated_config)
|
|
|
|
# Migrate other sections if needed
|
|
migrated_config, journal_actions = self._migrate_journal_config(migrated_config)
|
|
all_migration_actions.extend(journal_actions)
|
|
|
|
migrated_config, output_actions = self._migrate_output_config(migrated_config)
|
|
all_migration_actions.extend(output_actions)
|
|
|
|
migrated_config, analysis_actions = self._migrate_analysis_config(migrated_config)
|
|
all_migration_actions.extend(analysis_actions)
|
|
|
|
migrated_config, logging_actions = self._migrate_logging_config(migrated_config)
|
|
all_migration_actions.extend(logging_actions)
|
|
|
|
# Log overall migration summary
|
|
if all_migration_actions:
|
|
self.logger.info(f"Configuration migration completed with {len(all_migration_actions)} changes")
|
|
self._log_migration_summary(all_migration_actions)
|
|
else:
|
|
self.logger.debug("No configuration migration needed - all fields are up to date")
|
|
|
|
return migrated_config
|
|
|
|
def _migrate_journal_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]:
|
|
"""Migrate journal configuration section"""
|
|
migrated_config = config_dict.copy()
|
|
actions = []
|
|
|
|
if 'journal' not in migrated_config:
|
|
migrated_config['journal'] = self.default_values['journal'].copy()
|
|
actions.append("Created missing 'journal' configuration section with defaults")
|
|
else:
|
|
journal_config = migrated_config['journal']
|
|
|
|
# Add missing fields with defaults
|
|
for field, default_value in self.default_values['journal'].items():
|
|
if field not in journal_config:
|
|
journal_config[field] = default_value
|
|
actions.append(f"Added default journal.{field}: {default_value}")
|
|
|
|
return migrated_config, actions
|
|
|
|
def _migrate_output_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]:
|
|
"""Migrate output configuration section"""
|
|
migrated_config = config_dict.copy()
|
|
actions = []
|
|
|
|
if 'output' not in migrated_config:
|
|
migrated_config['output'] = self.default_values['output'].copy()
|
|
actions.append("Created missing 'output' configuration section with defaults")
|
|
else:
|
|
output_config = migrated_config['output']
|
|
|
|
# Add missing fields with defaults
|
|
for field, default_value in self.default_values['output'].items():
|
|
if field not in output_config:
|
|
output_config[field] = default_value
|
|
actions.append(f"Added default output.{field}: {default_value}")
|
|
|
|
return migrated_config, actions
|
|
|
|
def _migrate_analysis_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]:
|
|
"""Migrate analysis configuration section"""
|
|
migrated_config = config_dict.copy()
|
|
actions = []
|
|
|
|
if 'analysis' not in migrated_config:
|
|
migrated_config['analysis'] = self.default_values['analysis'].copy()
|
|
actions.append("Created missing 'analysis' configuration section with defaults")
|
|
else:
|
|
analysis_config = migrated_config['analysis']
|
|
|
|
# Add missing fields with defaults
|
|
for field, default_value in self.default_values['analysis'].items():
|
|
if field not in analysis_config:
|
|
analysis_config[field] = default_value
|
|
actions.append(f"Added default analysis.{field}: {default_value}")
|
|
|
|
return migrated_config, actions
|
|
|
|
def _migrate_logging_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]:
|
|
"""Migrate logging configuration section"""
|
|
migrated_config = config_dict.copy()
|
|
actions = []
|
|
|
|
if 'logging' not in migrated_config:
|
|
migrated_config['logging'] = self.default_values['logging'].copy()
|
|
actions.append("Created missing 'logging' configuration section with defaults")
|
|
else:
|
|
logging_config = migrated_config['logging']
|
|
|
|
# Add missing fields with defaults
|
|
for field, default_value in self.default_values['logging'].items():
|
|
if field not in logging_config:
|
|
logging_config[field] = default_value
|
|
actions.append(f"Added default logging.{field}: {default_value}")
|
|
|
|
return migrated_config, actions
|
|
|
|
def _log_migration_summary(self, actions: List[str]) -> None:
|
|
"""Log a summary of migration actions taken"""
|
|
self.logger.info("Migration summary:")
|
|
for action in actions:
|
|
self.logger.info(f" - {action}")
|
|
|
|
# Provide helpful information about new features
|
|
self.logger.info("")
|
|
self.logger.info("New configuration options are now available:")
|
|
self.logger.info(" - claude.api_url: Configure custom Claude API endpoints")
|
|
self.logger.info(" - claude.model: Enhanced model validation with suggestions")
|
|
self.logger.info(" - Environment variable support: Use ${VAR} and ${VAR:-default} patterns")
|
|
self.logger.info(" - See config.example.yaml for complete configuration examples")
|
|
|
|
def check_migration_needed(self, config_dict: Dict[str, Any]) -> bool:
|
|
"""
|
|
Check if configuration needs migration
|
|
|
|
Args:
|
|
config_dict: Configuration dictionary to check
|
|
|
|
Returns:
|
|
True if migration is needed, False otherwise
|
|
"""
|
|
# Check if Claude section needs migration
|
|
claude_config = config_dict.get('claude', {})
|
|
|
|
# Check for missing new fields
|
|
if 'api_url' not in claude_config:
|
|
return True
|
|
|
|
# Check for old model names that need migration
|
|
model = claude_config.get('model', '')
|
|
if model in self.model_migrations:
|
|
return True
|
|
|
|
# Check for missing other sections
|
|
required_sections = ['journal', 'output', 'analysis', 'logging']
|
|
for section in required_sections:
|
|
if section not in config_dict:
|
|
return True
|
|
|
|
# Check for missing fields in existing sections
|
|
section_config = config_dict[section]
|
|
default_fields = self.default_values.get(section, {})
|
|
for field in default_fields:
|
|
if field not in section_config:
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_migration_preview(self, config_dict: Dict[str, Any]) -> List[str]:
|
|
"""
|
|
Get a preview of what migration actions would be taken
|
|
|
|
Args:
|
|
config_dict: Configuration dictionary to analyze
|
|
|
|
Returns:
|
|
List of migration actions that would be taken
|
|
"""
|
|
preview_actions = []
|
|
|
|
# Check Claude configuration
|
|
claude_config = config_dict.get('claude', {})
|
|
|
|
if 'api_url' not in claude_config:
|
|
preview_actions.append(f"Would add default api_url: {self.default_values['claude']['api_url']}")
|
|
|
|
if 'model' not in claude_config:
|
|
preview_actions.append(f"Would add default model: {self.default_values['claude']['model']}")
|
|
elif claude_config['model'] in self.model_migrations:
|
|
old_model = claude_config['model']
|
|
new_model = self.model_migrations[old_model]
|
|
preview_actions.append(f"Would migrate model '{old_model}' to '{new_model}'")
|
|
|
|
# Check other sections
|
|
for section_name, section_defaults in self.default_values.items():
|
|
if section_name == 'claude':
|
|
continue # Already handled above
|
|
|
|
if section_name not in config_dict:
|
|
preview_actions.append(f"Would create missing '{section_name}' section with defaults")
|
|
else:
|
|
section_config = config_dict[section_name]
|
|
for field, default_value in section_defaults.items():
|
|
if field not in section_config:
|
|
preview_actions.append(f"Would add default {section_name}.{field}: {default_value}")
|
|
|
|
return preview_actions
|
|
|
|
def get_supported_model_names(self) -> List[str]:
|
|
"""
|
|
Get list of all supported model names (both old and new)
|
|
|
|
Returns:
|
|
List of supported model names
|
|
"""
|
|
# Current valid models
|
|
current_models = [
|
|
"claude-3-opus-20240229",
|
|
"claude-3-sonnet-20240229",
|
|
"claude-3-haiku-20240307",
|
|
"claude-3-5-sonnet-20241022",
|
|
"claude-3-5-haiku-20241022",
|
|
"claude-3-opus-latest",
|
|
"claude-3-sonnet-latest",
|
|
"claude-3-haiku-latest",
|
|
"claude-3-5-sonnet-latest",
|
|
"claude-3-5-haiku-latest"
|
|
]
|
|
|
|
# Legacy models that will be migrated
|
|
legacy_models = list(self.model_migrations.keys())
|
|
|
|
return current_models + legacy_models |