mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-17 17:56:46 +00:00
fix: Address invalid JSON field in installation suite
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
6425be82eb
commit
625088df64
@ -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}")
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: {
|
||||
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]:
|
||||
"""
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user