Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
171 lines
5.1 KiB
Python
171 lines
5.1 KiB
Python
"""
|
|
ConfigService - Project Configuration Management
|
|
|
|
Handles loading, saving, and validating project-specific .dss/config.json files.
|
|
Uses Pydantic for schema validation with sensible defaults.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any
|
|
from pydantic import BaseModel, Field
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# === Configuration Schema ===
|
|
|
|
class FigmaConfig(BaseModel):
|
|
"""Figma integration settings."""
|
|
file_id: Optional[str] = None
|
|
team_id: Optional[str] = None
|
|
|
|
|
|
class TokensConfig(BaseModel):
|
|
"""Design token export settings."""
|
|
output_path: str = "./tokens"
|
|
format: str = "css" # css | scss | json | js
|
|
|
|
|
|
class AIConfig(BaseModel):
|
|
"""AI assistant behavior settings."""
|
|
allowed_operations: List[str] = Field(default_factory=lambda: ["read", "write"])
|
|
context_files: List[str] = Field(default_factory=lambda: ["./README.md"])
|
|
max_file_size_kb: int = 500
|
|
|
|
|
|
class DSSConfig(BaseModel):
|
|
"""
|
|
Complete DSS project configuration schema.
|
|
|
|
Stored in: [project_root]/.dss/config.json
|
|
"""
|
|
schema_version: str = "1.0"
|
|
figma: FigmaConfig = Field(default_factory=FigmaConfig)
|
|
tokens: TokensConfig = Field(default_factory=TokensConfig)
|
|
ai: AIConfig = Field(default_factory=AIConfig)
|
|
|
|
class Config:
|
|
# Allow extra fields for forward compatibility
|
|
extra = "allow"
|
|
|
|
|
|
# === Config Service ===
|
|
|
|
class ConfigService:
|
|
"""
|
|
Service for managing project configuration files.
|
|
|
|
Loads .dss/config.json from project roots, validates against schema,
|
|
and provides defaults when config is missing.
|
|
"""
|
|
|
|
CONFIG_FILENAME = "config.json"
|
|
DSS_FOLDER = ".dss"
|
|
|
|
def __init__(self):
|
|
"""Initialize config service."""
|
|
logger.info("ConfigService initialized")
|
|
|
|
def get_config_path(self, project_root: str) -> Path:
|
|
"""Get path to config file for a project."""
|
|
return Path(project_root) / self.DSS_FOLDER / self.CONFIG_FILENAME
|
|
|
|
def get_config(self, project_root: str) -> DSSConfig:
|
|
"""
|
|
Load configuration for a project.
|
|
|
|
Args:
|
|
project_root: Absolute path to project root directory
|
|
|
|
Returns:
|
|
DSSConfig object (defaults if config file missing)
|
|
"""
|
|
config_path = self.get_config_path(project_root)
|
|
|
|
if config_path.exists():
|
|
try:
|
|
with open(config_path) as f:
|
|
data = json.load(f)
|
|
config = DSSConfig(**data)
|
|
logger.debug(f"Loaded config from {config_path}")
|
|
return config
|
|
except (json.JSONDecodeError, Exception) as e:
|
|
logger.warning(f"Failed to parse config at {config_path}: {e}")
|
|
# Fall through to return defaults
|
|
|
|
logger.debug(f"Using default config for {project_root}")
|
|
return DSSConfig()
|
|
|
|
def save_config(self, project_root: str, config: DSSConfig) -> None:
|
|
"""
|
|
Save configuration for a project.
|
|
|
|
Args:
|
|
project_root: Absolute path to project root directory
|
|
config: DSSConfig object to save
|
|
"""
|
|
config_path = self.get_config_path(project_root)
|
|
|
|
# Ensure .dss directory exists
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with open(config_path, 'w') as f:
|
|
json.dump(config.dict(), f, indent=2)
|
|
|
|
logger.info(f"Saved config to {config_path}")
|
|
|
|
def update_config(self, project_root: str, updates: Dict[str, Any]) -> DSSConfig:
|
|
"""
|
|
Update specific fields in project config.
|
|
|
|
Args:
|
|
project_root: Absolute path to project root directory
|
|
updates: Dictionary of fields to update
|
|
|
|
Returns:
|
|
Updated DSSConfig object
|
|
"""
|
|
config = self.get_config(project_root)
|
|
|
|
# Deep merge updates
|
|
config_dict = config.dict()
|
|
for key, value in updates.items():
|
|
if isinstance(value, dict) and isinstance(config_dict.get(key), dict):
|
|
config_dict[key].update(value)
|
|
else:
|
|
config_dict[key] = value
|
|
|
|
new_config = DSSConfig(**config_dict)
|
|
self.save_config(project_root, new_config)
|
|
return new_config
|
|
|
|
def init_config(self, project_root: str) -> DSSConfig:
|
|
"""
|
|
Initialize config file for a new project.
|
|
|
|
Creates .dss/ folder and config.json with defaults if not exists.
|
|
|
|
Args:
|
|
project_root: Absolute path to project root directory
|
|
|
|
Returns:
|
|
DSSConfig object (new or existing)
|
|
"""
|
|
config_path = self.get_config_path(project_root)
|
|
|
|
if config_path.exists():
|
|
logger.debug(f"Config already exists at {config_path}")
|
|
return self.get_config(project_root)
|
|
|
|
config = DSSConfig()
|
|
self.save_config(project_root, config)
|
|
logger.info(f"Initialized new config at {config_path}")
|
|
return config
|
|
|
|
def config_exists(self, project_root: str) -> bool:
|
|
"""Check if config file exists for a project."""
|
|
return self.get_config_path(project_root).exists()
|