From 328a0a016b347cbd9f3ad423364b9b51763434a1 Mon Sep 17 00:00:00 2001 From: kazuki Date: Mon, 20 Oct 2025 03:49:13 +0900 Subject: [PATCH] fix: unify metadata location and improve installer UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes ### Unified Metadata Location - All components now use `~/.claude/.superclaude-metadata.json` - Previously split between root and superclaude subdirectory - Automatic migration from old location on first load - Eliminates confusion from duplicate metadata files ### Improved Installation Messages - Changed WARNING to INFO for existing installations - Message now clearly states "will be updated" instead of implying problem - Reduces user confusion during reinstalls/updates ### Updated Makefile - `make install`: Development mode (uv, local source, editable) - `make install-release`: Production mode (pipx, from PyPI) - `make dev`: Alias for install - Improved help output with categorized commands ## Technical Details **Metadata Unification** (setup/services/settings.py): - SettingsService now always uses `~/.claude/.superclaude-metadata.json` - Added `_migrate_old_metadata()` for automatic migration - Deep merge strategy preserves existing data - Old file backed up as `.superclaude-metadata.json.migrated` **User File Protection**: - Verified: User-created files preserved during updates - Only SuperClaude-managed files (tracked in metadata) are updated - Obsolete framework files automatically removed ## Migration Path Existing installations automatically migrate on next `make install`: 1. Old metadata detected at `~/.claude/superclaude/.superclaude-metadata.json` 2. Merged into `~/.claude/.superclaude-metadata.json` 3. Old file backed up 4. No user action required 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Makefile | 50 ++++++++++++++++++++++------------- setup/cli/commands/install.py | 4 +-- setup/services/settings.py | 48 ++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 423a0ee..c66e460 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,21 @@ -.PHONY: install dev test clean lint format uninstall update translate help +.PHONY: install install-release dev test clean lint format uninstall update translate help -# Full installation (dependencies + SuperClaude components) +# Development installation (local source, editable) install: - @echo "Installing SuperClaude Framework..." + @echo "Installing SuperClaude Framework (development mode)..." uv pip install -e ".[dev]" uv run superclaude install -# Install dependencies and SuperClaude (for development) -dev: - @echo "Installing development dependencies..." - uv pip install -e ".[dev]" - @echo "Installing SuperClaude components..." - uv run superclaude install +# Production installation (from PyPI, recommended for users) +install-release: + @echo "Installing SuperClaude Framework (production mode)..." + @echo "Using pipx for isolated environment..." + pipx install SuperClaude + pipx upgrade SuperClaude + superclaude install + +# Alias for development installation +dev: install # Run tests test: @@ -69,13 +73,21 @@ translate: help: @echo "SuperClaude Framework - Available commands:" @echo "" - @echo " make install - Full installation (dependencies + components)" - @echo " make dev - Install development dependencies only" - @echo " make test - Run tests" - @echo " make lint - Run linter" - @echo " make format - Format code" - @echo " make clean - Clean build artifacts" - @echo " make uninstall - Uninstall SuperClaude components" - @echo " make update - Update SuperClaude components" - @echo " make translate - Translate README to Chinese and Japanese (requires Ollama)" - @echo " make help - Show this help message" + @echo "Installation:" + @echo " make install - Development installation (local source, editable with uv)" + @echo " make install-release - Production installation (from PyPI with pipx)" + @echo " make dev - Alias for 'make install'" + @echo "" + @echo "Development:" + @echo " make test - Run tests" + @echo " make lint - Run linter" + @echo " make format - Format code" + @echo " make clean - Clean build artifacts" + @echo "" + @echo "Maintenance:" + @echo " make uninstall - Uninstall SuperClaude components" + @echo " make update - Update SuperClaude components" + @echo "" + @echo "Documentation:" + @echo " make translate - Translate README to Chinese and Japanese (requires Ollama)" + @echo " make help - Show this help message" diff --git a/setup/cli/commands/install.py b/setup/cli/commands/install.py index ab0bc75..23d8611 100644 --- a/setup/cli/commands/install.py +++ b/setup/cli/commands/install.py @@ -720,8 +720,8 @@ def run(args: argparse.Namespace) -> int: # Check for existing installation if args.install_dir.exists() and not args.force: if not args.dry_run: - logger.warning( - f"Installation directory already exists: {args.install_dir}" + logger.info( + f"Existing installation found: {args.install_dir} (will be updated)" ) if not args.yes and not confirm( "Continue and update existing installation?", default=False diff --git a/setup/services/settings.py b/setup/services/settings.py index a45c78a..0119118 100644 --- a/setup/services/settings.py +++ b/setup/services/settings.py @@ -24,7 +24,12 @@ class SettingsService: """ self.install_dir = install_dir self.settings_file = install_dir / "settings.json" - self.metadata_file = install_dir / ".superclaude-metadata.json" + + # Always use ~/.claude/ for metadata (unified location) + # This ensures all components share the same metadata regardless of install_dir + from ..utils.paths import get_home_directory + self.metadata_root = get_home_directory() / ".claude" + self.metadata_file = self.metadata_root / ".superclaude-metadata.json" self.backup_dir = install_dir / "backups" / "settings" def load_settings(self) -> Dict[str, Any]: @@ -74,6 +79,9 @@ class SettingsService: Returns: Metadata dict (empty if file doesn't exist) """ + # Migrate from old location if needed + self._migrate_old_metadata() + if not self.metadata_file.exists(): return {} @@ -447,6 +455,44 @@ class SettingsService: return backup_file + def _migrate_old_metadata(self) -> None: + """ + Migrate metadata from old location (~/.claude/superclaude/) to unified location (~/.claude/) + This handles the transition from split metadata files to a single unified file. + """ + # Old metadata location (in superclaude subdirectory) + old_metadata_file = self.metadata_root / "superclaude" / ".superclaude-metadata.json" + + # If unified metadata already exists, skip migration + if self.metadata_file.exists(): + return + + # If old metadata exists, merge it into the new location + if old_metadata_file.exists(): + try: + with open(old_metadata_file, "r", encoding="utf-8") as f: + old_metadata = json.load(f) + + # Load current metadata (if any) + current_metadata = {} + if self.metadata_file.exists(): + with open(self.metadata_file, "r", encoding="utf-8") as f: + current_metadata = json.load(f) + + # Deep merge old into current + merged_metadata = self._deep_merge(current_metadata, old_metadata) + + # Save to unified location + self.save_metadata(merged_metadata) + + # Optionally backup old file (don't delete yet for safety) + backup_file = old_metadata_file.parent / ".superclaude-metadata.json.migrated" + shutil.copy2(old_metadata_file, backup_file) + + except Exception as e: + # Log but don't fail - old metadata migration is optional + pass + def _cleanup_old_backups(self, keep_count: int = 10) -> None: """ Remove old backup files, keeping only the most recent