- Create new dss/ Python package at project root - Move MCP core from tools/dss_mcp/ to dss/mcp/ - Move storage layer from tools/storage/ to dss/storage/ - Move domain logic from dss-mvp1/dss/ to dss/ - Move services from tools/api/services/ to dss/services/ - Move API server to apps/api/ - Move CLI to apps/cli/ - Move Storybook assets to storybook/ - Create unified dss/__init__.py with comprehensive exports - Merge configuration into dss/settings.py (Pydantic-based) - Create pyproject.toml for proper package management - Update startup scripts for new paths - Remove old tools/ and dss-mvp1/ directories Architecture changes: - DSS is now MCP-first with 40+ tools for Claude Code - Clean imports: from dss import Projects, Components, FigmaToolSuite - No more sys.path.insert() hacking - apps/ contains thin application wrappers (API, CLI) - Single unified Python package for all DSS logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
170 lines
5.9 KiB
Python
170 lines
5.9 KiB
Python
"""
|
|
DSS Project Models
|
|
|
|
Pydantic models for project configuration and state.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional, Union
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
|
|
class ProjectStatus(str, Enum):
|
|
"""Project lifecycle status."""
|
|
CREATED = "created"
|
|
CONFIGURED = "configured"
|
|
SYNCED = "synced"
|
|
BUILT = "built"
|
|
ERROR = "error"
|
|
|
|
|
|
class FigmaFile(BaseModel):
|
|
"""A single Figma file reference."""
|
|
key: str = Field(..., description="Figma file key from URL")
|
|
name: str = Field(..., description="Human-readable file name")
|
|
last_synced: Optional[datetime] = Field(None, description="Last sync timestamp")
|
|
thumbnail_url: Optional[str] = Field(None, description="Figma thumbnail URL")
|
|
|
|
class Config:
|
|
json_encoders = {datetime: lambda v: v.isoformat() if v else None}
|
|
|
|
|
|
class FigmaSource(BaseModel):
|
|
"""Figma project source configuration.
|
|
|
|
The team folder is the main Figma resource. Projects within the team
|
|
contain design files. The 'uikit' file (if present) is the primary
|
|
reference for design tokens.
|
|
"""
|
|
team_id: Optional[str] = Field(None, description="Figma team ID (main resource)")
|
|
project_id: Optional[str] = Field(None, description="Figma project ID within team")
|
|
project_name: Optional[str] = Field(None, description="Figma project name")
|
|
files: List[FigmaFile] = Field(default_factory=list, description="List of Figma files")
|
|
uikit_file_key: Optional[str] = Field(None, description="Key of the UIKit reference file")
|
|
auto_sync: bool = Field(False, description="Enable automatic sync on changes")
|
|
|
|
def add_file(self, key: str, name: str, thumbnail_url: Optional[str] = None) -> FigmaFile:
|
|
"""Add a file to the source."""
|
|
file = FigmaFile(key=key, name=name, thumbnail_url=thumbnail_url)
|
|
# Check for duplicates
|
|
if not any(f.key == key for f in self.files):
|
|
self.files.append(file)
|
|
return file
|
|
|
|
def get_file(self, key: str) -> Optional[FigmaFile]:
|
|
"""Get a file by key."""
|
|
for f in self.files:
|
|
if f.key == key:
|
|
return f
|
|
return None
|
|
|
|
|
|
class OutputConfig(BaseModel):
|
|
"""Output configuration for generated files."""
|
|
tokens_dir: str = Field("./tokens", description="Directory for token files")
|
|
themes_dir: str = Field("./themes", description="Directory for theme files")
|
|
components_dir: str = Field("./components", description="Directory for component files")
|
|
formats: List[str] = Field(
|
|
default_factory=lambda: ["css", "scss", "json"],
|
|
description="Output formats to generate"
|
|
)
|
|
|
|
@field_validator("formats")
|
|
@classmethod
|
|
def validate_formats(cls, v):
|
|
valid = {"css", "scss", "json", "js", "ts"}
|
|
for fmt in v:
|
|
if fmt not in valid:
|
|
raise ValueError(f"Invalid format: {fmt}. Must be one of {valid}")
|
|
return v
|
|
|
|
|
|
class ProjectConfig(BaseModel):
|
|
"""Main project configuration (ds.config.json)."""
|
|
name: str = Field(..., description="Project name")
|
|
version: str = Field("1.0.0", description="Project version")
|
|
description: Optional[str] = Field(None, description="Project description")
|
|
|
|
# Sources
|
|
figma: Optional[FigmaSource] = Field(None, description="Figma source configuration")
|
|
|
|
# Design system settings
|
|
skin: Optional[str] = Field(None, description="Base skin/theme to extend (e.g., 'shadcn', 'material')")
|
|
base_theme: str = Field("light", description="Default theme variant")
|
|
|
|
# Output configuration
|
|
output: OutputConfig = Field(default_factory=OutputConfig, description="Output settings")
|
|
|
|
# Metadata
|
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
updated_at: datetime = Field(default_factory=datetime.now)
|
|
|
|
class Config:
|
|
json_encoders = {datetime: lambda v: v.isoformat() if v else None}
|
|
|
|
|
|
class DSSProject(BaseModel):
|
|
"""
|
|
Complete DSS Project representation.
|
|
|
|
Combines configuration with runtime state.
|
|
"""
|
|
config: ProjectConfig = Field(..., description="Project configuration")
|
|
path: Path = Field(..., description="Absolute path to project directory")
|
|
status: ProjectStatus = Field(ProjectStatus.CREATED, description="Current project status")
|
|
|
|
# Runtime state
|
|
errors: List[str] = Field(default_factory=list, description="Error messages")
|
|
warnings: List[str] = Field(default_factory=list, description="Warning messages")
|
|
|
|
# Extracted data (populated after sync)
|
|
extracted_tokens: Optional[Dict[str, Any]] = Field(None, description="Tokens from sources")
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None,
|
|
Path: str,
|
|
}
|
|
|
|
@property
|
|
def config_path(self) -> Path:
|
|
"""Path to ds.config.json."""
|
|
return self.path / "ds.config.json"
|
|
|
|
@property
|
|
def tokens_path(self) -> Path:
|
|
"""Path to tokens directory."""
|
|
return self.path / self.config.output.tokens_dir
|
|
|
|
@property
|
|
def themes_path(self) -> Path:
|
|
"""Path to themes directory."""
|
|
return self.path / self.config.output.themes_dir
|
|
|
|
def to_config_dict(self) -> Dict[str, Any]:
|
|
"""Export configuration for saving to ds.config.json."""
|
|
return self.config.model_dump(mode="json", exclude_none=True)
|
|
|
|
@classmethod
|
|
def from_config_file(cls, config_path: Path) -> "DSSProject":
|
|
"""Load project from ds.config.json file."""
|
|
import json
|
|
|
|
if not config_path.exists():
|
|
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
|
|
with open(config_path, "r") as f:
|
|
config_data = json.load(f)
|
|
|
|
config = ProjectConfig(**config_data)
|
|
project_path = config_path.parent
|
|
|
|
return cls(
|
|
config=config,
|
|
path=project_path,
|
|
status=ProjectStatus.CONFIGURED,
|
|
)
|