Repository navigation
feat(config): implement config migration system with schema versioning #81
Copy link
Copy link
Open
Labels
featureNew feature or functionalityNew feature or functionality
Description
Summary
Implement a comprehensive configuration migration system for Titan CLI to handle schema evolution across versions. The system will support automatic migration of both global (~/.titan/config.toml) and project-level (.titan/config.toml) configurations with versioning, backup, and validation.
Problem Statement
Currently, Titan CLI's two-level configuration system lacks:
- ❌ Schema versioning
- ❌ Automatic migration on CLI upgrades
- ❌ Backward compatibility handling
- ❌ Safe field addition/removal mechanisms
This leads to breaking changes when users upgrade Titan CLI with existing configurations.
Proposed Solution
Implement a migration system with the following components:
Core Features
- Schema Versioning: Explicit
_meta.versionfield in all config files - Migration Scripts: Sequential upgrade scripts (e.g., 1.0.0 → 1.1.0 → 2.0.0)
- Automatic Migration: Run migrations on config load with automatic backups
- Validation: Post-migration Pydantic validation
- Safety Guarantees: Atomic writes, backups, rollback support
Architecture Components
titan_cli/core/migrations/
├── __init__.py
├── manager.py # MigrationManager orchestration
├── registry.py # Migration registry
├── version.py # Version comparison utilities
├── base.py # BaseMigration abstract class
└── scripts/
├── __init__.py
├── v1_0_0_to_v1_1_0.py
├── v1_1_0_to_v2_0_0.py
└── v2_0_0_to_v2_1_0.py
Migration Flow
- Load config.toml
- Check for
_meta.version(default to "1.0.0" if missing) - Compare with
CURRENT_SCHEMA_VERSION - If outdated:
- Backup original to
~/.titan/backups/config.toml.v{version}.bak - Run sequential migrations
- Update
_meta.version - Write atomically (temp file + rename)
- Backup original to
- Validate with Pydantic
- Config ready for use
CLI Commands
titan config migrate [--global|--project|--all]- Manual migration triggertitan config validate [--global|--project]- Validate without migratingtitan config version- Show schema versionstitan config backup list- List available backupstitan config backup restore <version>- Restore from backup
Implementation Phases
Phase 1: Initial Implementation (v0.2.0)
- ✅ Add
_meta.versionto config schema - ✅ Create migration infrastructure
- ✅ Implement first example migration
- ✅ Integrate with
TitanConfig
Phase 2: Production Hardening (v0.3.0)
- ✅ Comprehensive error handling
- ✅ Rollback mechanism
- ✅ CLI commands
- ✅ Migration logging
Phase 3: Advanced Features (v0.4.0+)
- ✅ Dry-run mode
- ✅ Plugin-specific migrations
- ✅ Config diff visualization
Safety Guarantees
- Always Backup: Automatic backup before migration
- Atomic Writes: Temp file + rename pattern
- Validation: Pydantic validation post-migration
- Graceful Degradation: Warnings instead of errors on failure
- Idempotent: Safe to run migrations multiple times
Testing Strategy
- Unit Tests: Individual migration script validation
- Integration Tests: Real TOML file migration scenarios
- Sequential Migration Tests: Verify multi-step upgrade paths
Related Files
titan_cli/core/config.py- Main configuration loadertitan_cli/core/models.py- Pydantic modelstitan_cli/core/errors.py- Error definitionsAGENTS.md- Configuration system documentation
Code Snippets
Example Config with Version
# Global config (~/.titan/config.toml)
[_meta]
version = "2.0.0" # Schema version, NOT Titan CLI version
[core]
project_root = "/home/user/projects"
default_editor = "vim"
[ai]
provider = "anthropic"
model = "claude-sonnet-4"BaseMigration Interface
# titan_cli/core/migrations/base.py
from abc import ABC, abstractmethod
from typing import Dict, Any
class BaseMigration(ABC):
"""Base class for all migration scripts."""
@property
@abstractmethod
def from_version(self) -> str:
"""Source version (e.g., '1.0.0')"""
pass
@property
@abstractmethod
def to_version(self) -> str:
"""Target version (e.g., '1.1.0')"""
pass
@abstractmethod
def migrate(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Migrate config from from_version to to_version.
Args:
config: The config dict to migrate
Returns:
The migrated config dict
"""
pass
def validate(self, config: Dict[str, Any]) -> bool:
"""Optional: Validate config before migration."""
return TrueExample Migration Script
# titan_cli/core/migrations/scripts/v1_0_0_to_v1_1_0.py
from typing import Dict, Any
from ..base import BaseMigration
class MigrateV1_0_0ToV1_1_0(BaseMigration):
"""
Migration: 1.0.0 → 1.1.0
Changes:
- Add [core.default_editor] with default "vim"
- Rename [ai.api_key] to [ai.anthropic_api_key]
"""
@property
def from_version(self) -> str:
return "1.0.0"
@property
def to_version(self) -> str:
return "1.1.0"
def migrate(self, config: Dict[str, Any]) -> Dict[str, Any]:
# Add default_editor if missing
if "core" not in config:
config["core"] = {}
if "default_editor" not in config["core"]:
config["core"]["default_editor"] = "vim"
# Rename ai.api_key to ai.anthropic_api_key
if "ai" in config and "api_key" in config["ai"]:
config["ai"]["anthropic_api_key"] = config["ai"].pop("api_key")
return configMigrationManager
# titan_cli/core/migrations/manager.py
from pathlib import Path
from typing import Dict, Any, Optional
import tomli
import tomli_w
import shutil
from .registry import MigrationRegistry
from .version import Version
class MigrationManager:
"""Manages config schema migrations."""
CURRENT_SCHEMA_VERSION = "2.0.0"
def __init__(self, registry: Optional[MigrationRegistry] = None):
self.registry = registry or MigrationRegistry()
def migrate_file(self, config_path: Path) -> bool:
"""
Migrate a config file to the latest schema version.
Args:
config_path: Path to the config.toml file
Returns:
True if migration was performed, False otherwise
"""
if not config_path.exists():
return False
# Load config
with open(config_path, "rb") as f:
config = tomli.load(f)
# Get current version
current_version = config.get("_meta", {}).get("version", "1.0.0")
#Metadata
Metadata
Assignees
Labels
featureNew feature or functionalityNew feature or functionality
Activity