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:
windyboy
2025-12-31 17:55:10 +08:00
parent 3200ad3dd5
commit f7e54692a9
67 changed files with 23088 additions and 0 deletions
+349
View File
@@ -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