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
299 lines
9.9 KiB
Python
299 lines
9.9 KiB
Python
"""
|
|
Base classes and data structures for code analysis.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import List, Dict, Any, Optional, Set
|
|
from pathlib import Path
|
|
|
|
|
|
class QuickWinType(str, Enum):
|
|
"""Types of quick-win improvements."""
|
|
INLINE_STYLE = "inline_style" # Inline styles that can be extracted
|
|
DUPLICATE_VALUE = "duplicate_value" # Duplicate color/spacing values
|
|
UNUSED_STYLE = "unused_style" # Unused CSS/SCSS
|
|
HARDCODED_VALUE = "hardcoded_value" # Hardcoded values that should be tokens
|
|
NAMING_INCONSISTENCY = "naming" # Inconsistent naming patterns
|
|
DEPRECATED_PATTERN = "deprecated" # Deprecated styling patterns
|
|
ACCESSIBILITY = "accessibility" # A11y improvements
|
|
PERFORMANCE = "performance" # Performance improvements
|
|
|
|
|
|
class QuickWinPriority(str, Enum):
|
|
"""Priority levels for quick-wins."""
|
|
CRITICAL = "critical" # Must fix - breaking issues
|
|
HIGH = "high" # Should fix - significant improvement
|
|
MEDIUM = "medium" # Nice to fix - moderate improvement
|
|
LOW = "low" # Optional - minor improvement
|
|
|
|
|
|
class StylingApproach(str, Enum):
|
|
"""Detected styling approaches in a project."""
|
|
CSS_MODULES = "css-modules"
|
|
STYLED_COMPONENTS = "styled-components"
|
|
EMOTION = "emotion"
|
|
TAILWIND = "tailwind"
|
|
INLINE_STYLES = "inline-styles"
|
|
CSS_IN_JS = "css-in-js"
|
|
SASS_SCSS = "sass-scss"
|
|
LESS = "less"
|
|
VANILLA_CSS = "vanilla-css"
|
|
CSS_VARIABLES = "css-variables"
|
|
|
|
|
|
class Framework(str, Enum):
|
|
"""Detected UI frameworks."""
|
|
REACT = "react"
|
|
NEXT = "next"
|
|
VUE = "vue"
|
|
NUXT = "nuxt"
|
|
ANGULAR = "angular"
|
|
SVELTE = "svelte"
|
|
SOLID = "solid"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
@dataclass
|
|
class Location:
|
|
"""Represents a location in source code."""
|
|
file_path: str
|
|
line: int
|
|
column: int = 0
|
|
end_line: Optional[int] = None
|
|
end_column: Optional[int] = None
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.file_path}:{self.line}"
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"file": self.file_path,
|
|
"line": self.line,
|
|
"column": self.column,
|
|
"end_line": self.end_line,
|
|
"end_column": self.end_column,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class StyleFile:
|
|
"""Represents a style file in the project."""
|
|
path: str
|
|
type: str # css, scss, less, styled, etc.
|
|
size_bytes: int = 0
|
|
line_count: int = 0
|
|
variable_count: int = 0
|
|
selector_count: int = 0
|
|
imports: List[str] = field(default_factory=list)
|
|
imported_by: List[str] = field(default_factory=list)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"path": self.path,
|
|
"type": self.type,
|
|
"size_bytes": self.size_bytes,
|
|
"line_count": self.line_count,
|
|
"variable_count": self.variable_count,
|
|
"selector_count": self.selector_count,
|
|
"imports": self.imports,
|
|
"imported_by": self.imported_by,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class ComponentInfo:
|
|
"""Information about a React component."""
|
|
name: str
|
|
path: str
|
|
type: str = "functional" # functional, class, forwardRef, memo
|
|
props: List[str] = field(default_factory=list)
|
|
has_styles: bool = False
|
|
style_files: List[str] = field(default_factory=list)
|
|
inline_style_count: int = 0
|
|
imports: List[str] = field(default_factory=list)
|
|
exports: List[str] = field(default_factory=list)
|
|
children: List[str] = field(default_factory=list) # Child components used
|
|
line_count: int = 0
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"name": self.name,
|
|
"path": self.path,
|
|
"type": self.type,
|
|
"props": self.props,
|
|
"has_styles": self.has_styles,
|
|
"style_files": self.style_files,
|
|
"inline_style_count": self.inline_style_count,
|
|
"imports": self.imports,
|
|
"exports": self.exports,
|
|
"children": self.children,
|
|
"line_count": self.line_count,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class StylePattern:
|
|
"""A detected style pattern in code."""
|
|
type: StylingApproach
|
|
locations: List[Location] = field(default_factory=list)
|
|
count: int = 0
|
|
examples: List[str] = field(default_factory=list)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"type": self.type.value,
|
|
"count": self.count,
|
|
"locations": [loc.to_dict() for loc in self.locations[:10]],
|
|
"examples": self.examples[:5],
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class TokenCandidate:
|
|
"""A value that could be extracted as a design token."""
|
|
value: str # The actual value (e.g., "#3B82F6")
|
|
suggested_name: str # Suggested token name
|
|
category: str # colors, spacing, typography, etc.
|
|
occurrences: int = 1 # How many times it appears
|
|
locations: List[Location] = field(default_factory=list)
|
|
confidence: float = 0.0 # 0-1 confidence score
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"value": self.value,
|
|
"suggested_name": self.suggested_name,
|
|
"category": self.category,
|
|
"occurrences": self.occurrences,
|
|
"locations": [loc.to_dict() for loc in self.locations[:5]],
|
|
"confidence": self.confidence,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class QuickWin:
|
|
"""A quick improvement opportunity."""
|
|
type: QuickWinType
|
|
priority: QuickWinPriority
|
|
title: str
|
|
description: str
|
|
location: Optional[Location] = None
|
|
affected_files: List[str] = field(default_factory=list)
|
|
estimated_impact: str = "" # e.g., "Remove 50 lines of duplicate code"
|
|
fix_suggestion: str = "" # Suggested fix
|
|
auto_fixable: bool = False # Can be auto-fixed
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"type": self.type.value,
|
|
"priority": self.priority.value,
|
|
"title": self.title,
|
|
"description": self.description,
|
|
"location": self.location.to_dict() if self.location else None,
|
|
"affected_files": self.affected_files,
|
|
"estimated_impact": self.estimated_impact,
|
|
"fix_suggestion": self.fix_suggestion,
|
|
"auto_fixable": self.auto_fixable,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class ProjectAnalysis:
|
|
"""Complete analysis result for a project."""
|
|
# Basic info
|
|
project_path: str
|
|
analyzed_at: datetime = field(default_factory=datetime.now)
|
|
|
|
# Framework detection
|
|
framework: Framework = Framework.UNKNOWN
|
|
framework_version: str = ""
|
|
|
|
# Styling detection
|
|
styling_approaches: List[StylePattern] = field(default_factory=list)
|
|
primary_styling: Optional[StylingApproach] = None
|
|
|
|
# Components
|
|
components: List[ComponentInfo] = field(default_factory=list)
|
|
component_count: int = 0
|
|
|
|
# Style files
|
|
style_files: List[StyleFile] = field(default_factory=list)
|
|
style_file_count: int = 0
|
|
|
|
# Issues and opportunities
|
|
inline_style_locations: List[Location] = field(default_factory=list)
|
|
token_candidates: List[TokenCandidate] = field(default_factory=list)
|
|
quick_wins: List[QuickWin] = field(default_factory=list)
|
|
|
|
# Dependency graph
|
|
dependency_graph: Dict[str, List[str]] = field(default_factory=dict)
|
|
|
|
# Statistics
|
|
stats: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
def __post_init__(self):
|
|
if not self.stats:
|
|
self.stats = {
|
|
"total_files_scanned": 0,
|
|
"total_lines": 0,
|
|
"component_count": 0,
|
|
"style_file_count": 0,
|
|
"inline_style_count": 0,
|
|
"token_candidates": 0,
|
|
"quick_wins_count": 0,
|
|
}
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"project_path": self.project_path,
|
|
"analyzed_at": self.analyzed_at.isoformat(),
|
|
"framework": self.framework.value,
|
|
"framework_version": self.framework_version,
|
|
"styling_approaches": [sp.to_dict() for sp in self.styling_approaches],
|
|
"primary_styling": self.primary_styling.value if self.primary_styling else None,
|
|
"component_count": self.component_count,
|
|
"style_file_count": self.style_file_count,
|
|
"inline_style_count": len(self.inline_style_locations),
|
|
"token_candidates_count": len(self.token_candidates),
|
|
"quick_wins_count": len(self.quick_wins),
|
|
"stats": self.stats,
|
|
}
|
|
|
|
def summary(self) -> str:
|
|
"""Generate human-readable summary."""
|
|
lines = [
|
|
f"Project Analysis: {self.project_path}",
|
|
"=" * 50,
|
|
f"Framework: {self.framework.value} {self.framework_version}",
|
|
f"Components: {self.component_count}",
|
|
f"Style files: {self.style_file_count}",
|
|
"",
|
|
"Styling Approaches:",
|
|
]
|
|
|
|
for sp in self.styling_approaches:
|
|
lines.append(f" • {sp.type.value}: {sp.count} occurrences")
|
|
|
|
lines.extend([
|
|
"",
|
|
f"Inline styles found: {len(self.inline_style_locations)}",
|
|
f"Token candidates: {len(self.token_candidates)}",
|
|
f"Quick wins: {len(self.quick_wins)}",
|
|
"",
|
|
"Quick Wins by Priority:",
|
|
])
|
|
|
|
by_priority = {}
|
|
for qw in self.quick_wins:
|
|
if qw.priority not in by_priority:
|
|
by_priority[qw.priority] = []
|
|
by_priority[qw.priority].append(qw)
|
|
|
|
for priority in [QuickWinPriority.CRITICAL, QuickWinPriority.HIGH,
|
|
QuickWinPriority.MEDIUM, QuickWinPriority.LOW]:
|
|
if priority in by_priority:
|
|
lines.append(f" [{priority.value.upper()}] {len(by_priority[priority])} items")
|
|
|
|
return "\n".join(lines)
|