Initial project setup: Obsidian intelligent journal organizer
- 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
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user