From 625088df64f600de35d69fa6ed1c4fa2cacb646f Mon Sep 17 00:00:00 2001 From: NomenAK Date: Mon, 14 Jul 2025 16:34:20 +0200 Subject: [PATCH] fix: Address invalid JSON field in installation suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Separate SuperClaude metadata from Claude Code settings.json - Create .superclaude-metadata.json for framework-specific data - Fix JSON validation issues with settings management - Update all components to use proper metadata storage - Maintain compatibility with Claude Code settings format - Add migration support for existing installations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- setup/base/installer.py | 5 +- setup/components/commands.py | 33 +++++--- setup/components/core.py | 46 +++++++---- setup/components/mcp.py | 62 ++++++++++---- setup/core/settings_manager.py | 147 ++++++++++++++++++++++++++++----- setup/operations/backup.py | 4 +- setup/operations/uninstall.py | 4 +- setup/operations/update.py | 4 +- 8 files changed, 229 insertions(+), 76 deletions(-) diff --git a/setup/base/installer.py b/setup/base/installer.py index 1d7ce04..2d68b3e 100644 --- a/setup/base/installer.py +++ b/setup/base/installer.py @@ -217,7 +217,7 @@ class Installer: if success: self.installed_components.add(component_name) - self._update_settings_registry(component) + # Component handles its own metadata registration else: self.failed_components.add(component_name) @@ -296,8 +296,7 @@ class Installer: return True else: success = component.uninstall() - if success: - self._remove_from_settings_registry(component_name) + # Component handles its own metadata removal return success except Exception as e: print(f"Error uninstalling {component_name}: {e}") diff --git a/setup/components/commands.py b/setup/components/commands.py index 489ac18..2c222ad 100644 --- a/setup/components/commands.py +++ b/setup/components/commands.py @@ -97,8 +97,8 @@ class CommandsComponent(Component): return files - def get_settings_modifications(self) -> Dict[str, Any]: - """Get settings modifications""" + def get_metadata_modifications(self) -> Dict[str, Any]: + """Get metadata modifications for commands component""" return { "components": { "commands": { @@ -109,6 +109,11 @@ class CommandsComponent(Component): } } + def get_settings_modifications(self) -> Dict[str, Any]: + """Get settings.json modifications (now only Claude Code compatible settings)""" + # Return empty dict as we don't modify Claude Code settings + return {} + def install(self, config: Dict[str, Any]) -> bool: """Install commands component""" try: @@ -155,13 +160,17 @@ class CommandsComponent(Component): self.logger.error(f"Only {success_count}/{len(files_to_install)} command files copied successfully") return False - # Update settings.json + # Update metadata try: - settings_mods = self.get_settings_modifications() - self.settings_manager.update_settings(settings_mods) - self.logger.info("Updated settings.json with commands component registration") + # Add component registration to metadata + self.settings_manager.add_component_registration("commands", { + "version": "3.0.0", + "category": "commands", + "files_count": len(self.command_files) + }) + self.logger.info("Updated metadata with commands component registration") except Exception as e: - self.logger.error(f"Failed to update settings.json: {e}") + self.logger.error(f"Failed to update metadata: {e}") return False self.logger.success(f"Commands component installed successfully ({success_count} command files)") @@ -198,13 +207,13 @@ class CommandsComponent(Component): except Exception as e: self.logger.warning(f"Could not remove commands directory: {e}") - # Update settings.json to remove commands component + # Update metadata to remove commands component try: if self.settings_manager.is_component_installed("commands"): self.settings_manager.remove_component_registration("commands") - self.logger.info("Removed commands component from settings.json") + self.logger.info("Removed commands component from metadata") except Exception as e: - self.logger.warning(f"Could not update settings.json: {e}") + self.logger.warning(f"Could not update metadata: {e}") self.logger.success(f"Commands component uninstalled ({removed_count} files removed)") return True @@ -292,9 +301,9 @@ class CommandsComponent(Component): elif not file_path.is_file(): errors.append(f"Command file is not a regular file: {filename}") - # Check settings.json registration + # Check metadata registration if not self.settings_manager.is_component_installed("commands"): - errors.append("Commands component not registered in settings.json") + errors.append("Commands component not registered in metadata") else: # Check version matches installed_version = self.settings_manager.get_component_version("commands") diff --git a/setup/components/core.py b/setup/components/core.py index 416ff9f..f147143 100644 --- a/setup/components/core.py +++ b/setup/components/core.py @@ -92,8 +92,8 @@ class CoreComponent(Component): return files - def get_settings_modifications(self) -> Dict[str, Any]: - """Get settings.json modifications""" + def get_metadata_modifications(self) -> Dict[str, Any]: + """Get metadata modifications for SuperClaude""" return { "framework": { "version": "3.0.0", @@ -110,6 +110,11 @@ class CoreComponent(Component): } } + def get_settings_modifications(self) -> Dict[str, Any]: + """Get settings.json modifications (now only Claude Code compatible settings)""" + # Return empty dict as we don't modify Claude Code settings + return {} + def install(self, config: Dict[str, Any]) -> bool: """Install core component""" try: @@ -155,13 +160,20 @@ class CoreComponent(Component): self.logger.error(f"Only {success_count}/{len(files_to_install)} files copied successfully") return False - # Create or update settings.json + # Create or update metadata try: - settings_mods = self.get_settings_modifications() - self.settings_manager.update_settings(settings_mods) - self.logger.info("Updated settings.json with framework configuration") + metadata_mods = self.get_metadata_modifications() + # Update metadata directly + existing_metadata = self.settings_manager.load_metadata() + merged_metadata = self.settings_manager._deep_merge(existing_metadata, metadata_mods) + self.settings_manager.save_metadata(merged_metadata) + self.logger.info("Updated metadata with framework configuration") + + # Migrate any existing SuperClaude data from settings.json + if self.settings_manager.migrate_superclaude_data(): + self.logger.info("Migrated existing SuperClaude data from settings.json") except Exception as e: - self.logger.error(f"Failed to update settings.json: {e}") + self.logger.error(f"Failed to update metadata: {e}") return False # Create additional directories for other components @@ -193,13 +205,13 @@ class CoreComponent(Component): else: self.logger.warning(f"Could not remove {filename}") - # Update settings.json to remove core component + # Update metadata to remove core component try: if self.settings_manager.is_component_installed("core"): self.settings_manager.remove_component_registration("core") - self.logger.info("Removed core component from settings.json") + self.logger.info("Removed core component from metadata") except Exception as e: - self.logger.warning(f"Could not update settings.json: {e}") + self.logger.warning(f"Could not update metadata: {e}") self.logger.success(f"Core component uninstalled ({removed_count} files removed)") return True @@ -278,9 +290,9 @@ class CoreComponent(Component): elif not file_path.is_file(): errors.append(f"Framework file is not a regular file: {filename}") - # Check settings.json registration + # Check metadata registration if not self.settings_manager.is_component_installed("core"): - errors.append("Core component not registered in settings.json") + errors.append("Core component not registered in metadata") else: # Check version matches installed_version = self.settings_manager.get_component_version("core") @@ -288,18 +300,18 @@ class CoreComponent(Component): if installed_version != expected_version: errors.append(f"Version mismatch: installed {installed_version}, expected {expected_version}") - # Check settings.json structure + # Check metadata structure try: - framework_config = self.settings_manager.get_setting("framework") + framework_config = self.settings_manager.get_metadata_setting("framework") if not framework_config: - errors.append("Missing framework configuration in settings.json") + errors.append("Missing framework configuration in metadata") else: required_keys = ["version", "name", "description"] for key in required_keys: if key not in framework_config: - errors.append(f"Missing framework.{key} in settings.json") + errors.append(f"Missing framework.{key} in metadata") except Exception as e: - errors.append(f"Could not validate settings.json: {e}") + errors.append(f"Could not validate metadata: {e}") return len(errors) == 0, errors diff --git a/setup/components/mcp.py b/setup/components/mcp.py index 54bbef8..a6c144e 100644 --- a/setup/components/mcp.py +++ b/setup/components/mcp.py @@ -131,8 +131,8 @@ class MCPComponent(Component): """Get files to install (none for MCP component)""" return [] - def get_settings_modifications(self) -> Dict[str, Any]: - """Get settings modifications""" + def get_metadata_modifications(self) -> Dict[str, Any]: + """Get metadata modifications for MCP component""" return { "components": { "mcp": { @@ -148,6 +148,11 @@ class MCPComponent(Component): } } + def get_settings_modifications(self) -> Dict[str, Any]: + """Get settings.json modifications (now only Claude Code compatible settings)""" + # Return empty dict as we don't modify Claude Code settings + return {} + def _check_mcp_server_installed(self, server_name: str) -> bool: """Check if MCP server is already installed""" try: @@ -292,13 +297,27 @@ class MCPComponent(Component): self.logger.error(f"Required MCP server {server_name} failed to install") return False - # Update settings.json + # Update metadata try: - settings_mods = self.get_settings_modifications() - self.settings_manager.update_settings(settings_mods) - self.logger.info("Updated settings.json with MCP component registration") + # Add component registration to metadata + self.settings_manager.add_component_registration("mcp", { + "version": "3.0.0", + "category": "integration", + "servers_count": len(self.mcp_servers) + }) + + # Add MCP configuration to metadata + metadata = self.settings_manager.load_metadata() + metadata["mcp"] = { + "enabled": True, + "servers": list(self.mcp_servers.keys()), + "auto_update": False + } + self.settings_manager.save_metadata(metadata) + + self.logger.info("Updated metadata with MCP component registration") except Exception as e: - self.logger.error(f"Failed to update settings.json: {e}") + self.logger.error(f"Failed to update metadata: {e}") return False # Verify installation @@ -347,13 +366,18 @@ class MCPComponent(Component): if self._uninstall_mcp_server(server_name): uninstalled_count += 1 - # Update settings.json to remove MCP component + # Update metadata to remove MCP component try: if self.settings_manager.is_component_installed("mcp"): self.settings_manager.remove_component_registration("mcp") - self.logger.info("Removed MCP component from settings.json") + # Also remove MCP configuration from metadata + metadata = self.settings_manager.load_metadata() + if "mcp" in metadata: + del metadata["mcp"] + self.settings_manager.save_metadata(metadata) + self.logger.info("Removed MCP component from metadata") except Exception as e: - self.logger.warning(f"Could not update settings.json: {e}") + self.logger.warning(f"Could not update metadata: {e}") self.logger.success(f"MCP component uninstalled ({uninstalled_count} servers removed)") return True @@ -401,12 +425,18 @@ class MCPComponent(Component): self.logger.error(f"Error updating MCP server {server_name}: {e}") failed_servers.append(server_name) - # Update settings + # Update metadata try: - settings_mods = self.get_settings_modifications() - self.settings_manager.update_settings(settings_mods) + # Update component version in metadata + metadata = self.settings_manager.load_metadata() + if "components" in metadata and "mcp" in metadata["components"]: + metadata["components"]["mcp"]["version"] = target_version + metadata["components"]["mcp"]["servers_count"] = len(self.mcp_servers) + if "mcp" in metadata: + metadata["mcp"]["servers"] = list(self.mcp_servers.keys()) + self.settings_manager.save_metadata(metadata) except Exception as e: - self.logger.warning(f"Could not update settings.json: {e}") + self.logger.warning(f"Could not update metadata: {e}") if failed_servers: self.logger.warning(f"Some MCP servers failed to update: {failed_servers}") @@ -423,9 +453,9 @@ class MCPComponent(Component): """Validate MCP component installation""" errors = [] - # Check settings.json registration + # Check metadata registration if not self.settings_manager.is_component_installed("mcp"): - errors.append("MCP component not registered in settings.json") + errors.append("MCP component not registered in metadata") return False, errors # Check version matches diff --git a/setup/core/settings_manager.py b/setup/core/settings_manager.py index 37f971c..ee77b8e 100644 --- a/setup/core/settings_manager.py +++ b/setup/core/settings_manager.py @@ -23,6 +23,7 @@ class SettingsManager: """ self.install_dir = install_dir self.settings_file = install_dir / "settings.json" + self.metadata_file = install_dir / ".superclaude-metadata.json" self.backup_dir = install_dir / "backups" / "settings" def load_settings(self) -> Dict[str, Any]: @@ -63,6 +64,77 @@ class SettingsManager: except IOError as e: raise ValueError(f"Could not save settings to {self.settings_file}: {e}") + def load_metadata(self) -> Dict[str, Any]: + """ + Load SuperClaude metadata from .superclaude-metadata.json + + Returns: + Metadata dict (empty if file doesn't exist) + """ + if not self.metadata_file.exists(): + return {} + + try: + with open(self.metadata_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError) as e: + raise ValueError(f"Could not load metadata from {self.metadata_file}: {e}") + + def save_metadata(self, metadata: Dict[str, Any]) -> None: + """ + Save SuperClaude metadata to .superclaude-metadata.json + + Args: + metadata: Metadata dict to save + """ + # Ensure directory exists + self.metadata_file.parent.mkdir(parents=True, exist_ok=True) + + # Save with pretty formatting + try: + with open(self.metadata_file, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False, sort_keys=True) + except IOError as e: + raise ValueError(f"Could not save metadata to {self.metadata_file}: {e}") + + def migrate_superclaude_data(self) -> bool: + """ + Migrate SuperClaude-specific data from settings.json to metadata file + + Returns: + True if migration occurred, False if no data to migrate + """ + settings = self.load_settings() + + # SuperClaude-specific fields to migrate + superclaude_fields = ["components", "framework", "superclaude", "mcp"] + data_to_migrate = {} + fields_found = False + + # Extract SuperClaude data + for field in superclaude_fields: + if field in settings: + data_to_migrate[field] = settings[field] + fields_found = True + + if not fields_found: + return False + + # Load existing metadata (if any) and merge + existing_metadata = self.load_metadata() + merged_metadata = self._deep_merge(existing_metadata, data_to_migrate) + + # Save to metadata file + self.save_metadata(merged_metadata) + + # Remove SuperClaude fields from settings + clean_settings = {k: v for k, v in settings.items() if k not in superclaude_fields} + + # Save cleaned settings + self.save_settings(clean_settings, create_backup=True) + + return True + def merge_settings(self, modifications: Dict[str, Any]) -> Dict[str, Any]: """ Deep merge modifications into existing settings @@ -163,25 +235,26 @@ class SettingsManager: def add_component_registration(self, component_name: str, component_info: Dict[str, Any]) -> None: """ - Add component to registry in settings + Add component to registry in metadata Args: component_name: Name of component component_info: Component metadata dict """ - modification = { - "components": { - component_name: { - **component_info, - "installed_at": datetime.now().isoformat() - } - } + metadata = self.load_metadata() + if "components" not in metadata: + metadata["components"] = {} + + metadata["components"][component_name] = { + **component_info, + "installed_at": datetime.now().isoformat() } - self.update_settings(modification) + + self.save_metadata(metadata) def remove_component_registration(self, component_name: str) -> bool: """ - Remove component from registry in settings + Remove component from registry in metadata Args: component_name: Name of component to remove @@ -189,7 +262,12 @@ class SettingsManager: Returns: True if component was removed, False if not found """ - return self.remove_setting(f"components.{component_name}") + metadata = self.load_metadata() + if "components" in metadata and component_name in metadata["components"]: + del metadata["components"][component_name] + self.save_metadata(metadata) + return True + return False def get_installed_components(self) -> Dict[str, Dict[str, Any]]: """ @@ -198,7 +276,8 @@ class SettingsManager: Returns: Dict of component_name -> component_info """ - return self.get_setting("components", {}) + metadata = self.load_metadata() + return metadata.get("components", {}) def is_component_installed(self, component_name: str) -> bool: """ @@ -229,27 +308,51 @@ class SettingsManager: def update_framework_version(self, version: str) -> None: """ - Update SuperClaude framework version in settings + Update SuperClaude framework version in metadata Args: version: Framework version string """ - modification = { - "framework": { - "version": version, - "updated_at": datetime.now().isoformat() - } - } - self.update_settings(modification) + metadata = self.load_metadata() + if "framework" not in metadata: + metadata["framework"] = {} + + metadata["framework"]["version"] = version + metadata["framework"]["updated_at"] = datetime.now().isoformat() + + self.save_metadata(metadata) def get_framework_version(self) -> Optional[str]: """ - Get SuperClaude framework version from settings + Get SuperClaude framework version from metadata Returns: Version string or None if not set """ - return self.get_setting("framework.version") + metadata = self.load_metadata() + framework = metadata.get("framework", {}) + return framework.get("version") + + def get_metadata_setting(self, key_path: str, default: Any = None) -> Any: + """ + Get metadata value using dot-notation path + + Args: + key_path: Dot-separated path (e.g., "framework.version") + default: Default value if key not found + + Returns: + Metadata value or default + """ + metadata = self.load_metadata() + + try: + value = metadata + for key in key_path.split('.'): + value = value[key] + return value + except (KeyError, TypeError): + return default def _deep_merge(self, base: Dict[str, Any], overlay: Dict[str, Any]) -> Dict[str, Any]: """ diff --git a/setup/operations/backup.py b/setup/operations/backup.py index 6bd75fa..df35397 100644 --- a/setup/operations/backup.py +++ b/setup/operations/backup.py @@ -241,9 +241,9 @@ def create_backup_metadata(install_dir: Path) -> Dict[str, Any]: } try: - # Get installed components + # Get installed components from metadata settings_manager = SettingsManager(install_dir) - framework_config = settings_manager.get_setting("framework") + framework_config = settings_manager.get_metadata_setting("framework") if framework_config: metadata["framework_version"] = framework_config.get("version", "unknown") diff --git a/setup/operations/uninstall.py b/setup/operations/uninstall.py index e22181b..b80d3ad 100644 --- a/setup/operations/uninstall.py +++ b/setup/operations/uninstall.py @@ -102,8 +102,8 @@ def get_installed_components(install_dir: Path) -> Dict[str, str]: settings_manager = SettingsManager(install_dir) components = {} - # Check for framework configuration - framework_config = settings_manager.get_setting("framework") + # Check for framework configuration in metadata + framework_config = settings_manager.get_metadata_setting("framework") if framework_config and "components" in framework_config: for component_name in framework_config["components"]: version = settings_manager.get_component_version(component_name) diff --git a/setup/operations/update.py b/setup/operations/update.py index aaf93d2..34df792 100644 --- a/setup/operations/update.py +++ b/setup/operations/update.py @@ -98,8 +98,8 @@ def get_installed_components(install_dir: Path) -> Dict[str, str]: settings_manager = SettingsManager(install_dir) components = {} - # Check for framework configuration - framework_config = settings_manager.get_setting("framework") + # Check for framework configuration in metadata + framework_config = settings_manager.get_metadata_setting("framework") if framework_config and "components" in framework_config: for component_name in framework_config["components"]: version = settings_manager.get_component_version(component_name)