Major refactor: Consolidate DSS into unified package structure

- 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>
This commit is contained in:
2025-12-10 12:43:18 -03:00
parent bbd67f88c4
commit 41fba59bf7
197 changed files with 3185 additions and 15500 deletions

441
dss/analyze/react.py Normal file
View File

@@ -0,0 +1,441 @@
"""
React Project Analyzer
Analyzes React codebases to extract component information,
detect patterns, and identify style usage.
"""
import re
from pathlib import Path
from typing import List, Dict, Any, Optional, Set, Tuple
from dataclasses import dataclass, field
from .base import (
ComponentInfo,
Location,
StylePattern,
StylingApproach,
)
# Patterns for React component detection
FUNCTIONAL_COMPONENT = re.compile(
r'(?:export\s+)?(?:const|let|var|function)\s+([A-Z][A-Za-z0-9]*)\s*(?::\s*(?:React\.)?FC)?'
r'\s*(?:=\s*(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>|\()',
re.MULTILINE
)
CLASS_COMPONENT = re.compile(
r'class\s+([A-Z][A-Za-z0-9]*)\s+extends\s+(?:React\.)?(?:Component|PureComponent)',
re.MULTILINE
)
FORWARD_REF = re.compile(
r'(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9]*)\s*=\s*(?:React\.)?forwardRef',
re.MULTILINE
)
MEMO_COMPONENT = re.compile(
r'(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9]*)\s*=\s*(?:React\.)?memo\(',
re.MULTILINE
)
# Import patterns
IMPORT_PATTERN = re.compile(
r'import\s+(?:\{[^}]+\}|\*\s+as\s+\w+|\w+)\s+from\s+["\']([^"\']+)["\']',
re.MULTILINE
)
STYLE_IMPORT = re.compile(
r'import\s+(?:(\w+)\s+from\s+)?["\']([^"\']+\.(?:css|scss|sass|less|styl))["\']',
re.MULTILINE
)
# Inline style patterns
INLINE_STYLE_OBJECT = re.compile(
r'style\s*=\s*\{\s*\{([^}]+)\}\s*\}',
re.MULTILINE | re.DOTALL
)
INLINE_STYLE_VAR = re.compile(
r'style\s*=\s*\{(\w+)\}',
re.MULTILINE
)
# Props extraction
PROPS_DESTRUCTURE = re.compile(
r'\(\s*\{\s*([^}]+)\s*\}\s*(?::\s*[^)]+)?\)',
re.MULTILINE
)
PROPS_INTERFACE = re.compile(
r'interface\s+\w*Props\s*\{([^}]+)\}',
re.MULTILINE | re.DOTALL
)
PROPS_TYPE = re.compile(
r'type\s+\w*Props\s*=\s*\{([^}]+)\}',
re.MULTILINE | re.DOTALL
)
class ReactAnalyzer:
"""
Analyzes React projects for component structure and style usage.
"""
def __init__(self, root_path: str):
self.root = Path(root_path).resolve()
async def analyze(
self,
component_files: Optional[List[Path]] = None
) -> List[ComponentInfo]:
"""
Analyze React components in the project.
Args:
component_files: Optional list of files to analyze.
If None, scans the project.
Returns:
List of ComponentInfo for each detected component.
"""
if component_files is None:
component_files = self._find_component_files()
components = []
for file_path in component_files:
try:
file_components = await self._analyze_file(file_path)
components.extend(file_components)
except Exception as e:
# Log error but continue
continue
return components
def _find_component_files(self) -> List[Path]:
"""Find all potential React component files."""
skip_dirs = {'node_modules', '.git', 'dist', 'build', '.next'}
component_files = []
for ext in ['*.jsx', '*.tsx']:
for path in self.root.rglob(ext):
if not any(skip in path.parts for skip in skip_dirs):
component_files.append(path)
# Also check .js/.ts files that look like components
for ext in ['*.js', '*.ts']:
for path in self.root.rglob(ext):
if any(skip in path.parts for skip in skip_dirs):
continue
# Skip config and utility files
if any(x in path.name.lower() for x in ['config', 'util', 'helper', 'hook', 'context']):
continue
# Check if PascalCase (likely component)
if path.stem[0].isupper():
component_files.append(path)
return component_files
async def _analyze_file(self, file_path: Path) -> List[ComponentInfo]:
"""Analyze a single file for React components."""
content = file_path.read_text(encoding='utf-8', errors='ignore')
components = []
# Find all components in the file
component_matches = []
# Functional components
for match in FUNCTIONAL_COMPONENT.finditer(content):
name = match.group(1)
if self._is_valid_component_name(name):
component_matches.append((name, 'functional', match.start()))
# Class components
for match in CLASS_COMPONENT.finditer(content):
name = match.group(1)
component_matches.append((name, 'class', match.start()))
# forwardRef components
for match in FORWARD_REF.finditer(content):
name = match.group(1)
component_matches.append((name, 'forwardRef', match.start()))
# memo components
for match in MEMO_COMPONENT.finditer(content):
name = match.group(1)
component_matches.append((name, 'memo', match.start()))
# Dedupe by name (keep first occurrence)
seen_names = set()
unique_matches = []
for name, comp_type, pos in component_matches:
if name not in seen_names:
seen_names.add(name)
unique_matches.append((name, comp_type, pos))
# Extract imports (shared across all components in file)
imports = self._extract_imports(content)
style_files = self._extract_style_imports(content)
inline_styles = self._find_inline_styles(content)
# Create ComponentInfo for each
for name, comp_type, pos in unique_matches:
# Extract props for this component
props = self._extract_props(content, name)
# Find child components used
children = self._find_child_components(content, seen_names)
# Check if component has styles
has_styles = bool(style_files) or bool(inline_styles)
components.append(ComponentInfo(
name=name,
path=str(file_path.relative_to(self.root)),
type=comp_type,
props=props,
has_styles=has_styles,
style_files=style_files,
inline_style_count=len(inline_styles),
imports=imports,
exports=self._find_exports(content, name),
children=children,
line_count=content.count('\n') + 1,
))
return components
def _is_valid_component_name(self, name: str) -> bool:
"""Check if a name is a valid React component name."""
# Must be PascalCase
if not name[0].isupper():
return False
# Filter out common non-component patterns
invalid_names = {
'React', 'Component', 'PureComponent', 'Fragment',
'Suspense', 'Provider', 'Consumer', 'Context',
'Error', 'ErrorBoundary', 'Wrapper', 'Container',
'Props', 'State', 'Type', 'Interface',
}
return name not in invalid_names
def _extract_imports(self, content: str) -> List[str]:
"""Extract import paths from file."""
imports = []
for match in IMPORT_PATTERN.finditer(content):
import_path = match.group(1)
# Skip node_modules style imports for brevity
if not import_path.startswith('.') and '/' not in import_path:
continue
imports.append(import_path)
return imports
def _extract_style_imports(self, content: str) -> List[str]:
"""Extract style file imports."""
style_files = []
for match in STYLE_IMPORT.finditer(content):
style_path = match.group(2)
style_files.append(style_path)
return style_files
def _find_inline_styles(self, content: str) -> List[Location]:
"""Find inline style usage locations."""
locations = []
# style={{ ... }}
for match in INLINE_STYLE_OBJECT.finditer(content):
line = content[:match.start()].count('\n') + 1
locations.append(Location(
file_path="", # Will be set by caller
line=line,
))
return locations
def _extract_props(self, content: str, component_name: str) -> List[str]:
"""Extract props for a component."""
props = set()
# Look for destructured props
for match in PROPS_DESTRUCTURE.finditer(content):
props_str = match.group(1)
# Extract prop names from destructuring
for prop in re.findall(r'(\w+)(?:\s*[=:])?', props_str):
if prop and not prop[0].isupper(): # Skip types
props.add(prop)
# Look for Props interface/type
for pattern in [PROPS_INTERFACE, PROPS_TYPE]:
for match in pattern.finditer(content):
props_str = match.group(1)
# Extract prop names
for line in props_str.split('\n'):
prop_match = re.match(r'\s*(\w+)\s*[?:]', line)
if prop_match:
props.add(prop_match.group(1))
return list(props)
def _find_child_components(
self,
content: str,
current_components: Set[str]
) -> List[str]:
"""Find child components used in JSX."""
children = set()
# Find JSX elements that look like components (PascalCase)
jsx_pattern = re.compile(r'<([A-Z][A-Za-z0-9]*)')
for match in jsx_pattern.finditer(content):
component_name = match.group(1)
# Skip current file's components and React built-ins
if component_name not in current_components:
if component_name not in {'Fragment', 'Suspense', 'Provider'}:
children.add(component_name)
return list(children)
def _find_exports(self, content: str, component_name: str) -> List[str]:
"""Find export type for component."""
exports = []
# Default export
if re.search(rf'export\s+default\s+{component_name}\b', content):
exports.append('default')
if re.search(rf'export\s+default\s+(?:function|const)\s+{component_name}\b', content):
exports.append('default')
# Named export
if re.search(rf'export\s+(?:const|function|class)\s+{component_name}\b', content):
exports.append('named')
if re.search(r'export\s*\{[^}]*\b' + re.escape(component_name) + r'\b[^}]*\}', content):
exports.append('named')
return exports
async def find_inline_styles(self, path: Optional[str] = None) -> List[Dict[str, Any]]:
"""
Find all inline style usage in the project.
Returns list of inline style occurrences with:
- file path
- line number
- style content
- component name (if detectable)
"""
search_path = Path(path) if path else self.root
results = []
for ext in ['*.jsx', '*.tsx', '*.js', '*.ts']:
for file_path in search_path.rglob(ext):
if any(skip in file_path.parts for skip in
{'node_modules', '.git', 'dist', 'build'}):
continue
try:
content = file_path.read_text(encoding='utf-8', errors='ignore')
# Find style={{ ... }}
for match in INLINE_STYLE_OBJECT.finditer(content):
line = content[:match.start()].count('\n') + 1
style_content = match.group(1).strip()
results.append({
'file': str(file_path.relative_to(self.root)),
'line': line,
'content': style_content[:200],
'type': 'object',
})
# Find style={variable}
for match in INLINE_STYLE_VAR.finditer(content):
line = content[:match.start()].count('\n') + 1
var_name = match.group(1)
results.append({
'file': str(file_path.relative_to(self.root)),
'line': line,
'content': f'style={{{var_name}}}',
'type': 'variable',
'variable': var_name,
})
except Exception:
continue
return results
async def get_component_tree(self) -> Dict[str, List[str]]:
"""
Build component dependency tree.
Returns dict mapping component names to their child components.
"""
components = await self.analyze()
tree = {}
for comp in components:
tree[comp.name] = comp.children
return tree
async def find_style_patterns(self) -> Dict[str, List[Dict]]:
"""
Find different styling patterns used across the project.
Returns dict with pattern types and their occurrences.
"""
patterns = {
'inline_styles': [],
'css_modules': [],
'styled_components': [],
'emotion': [],
'tailwind': [],
'css_classes': [],
}
component_files = self._find_component_files()
for file_path in component_files:
try:
content = file_path.read_text(encoding='utf-8', errors='ignore')
rel_path = str(file_path.relative_to(self.root))
# CSS Modules
if re.search(r'import\s+\w+\s+from\s+["\'].*\.module\.', content):
patterns['css_modules'].append({'file': rel_path})
# styled-components
if re.search(r'styled\.|from\s+["\']styled-components', content):
patterns['styled_components'].append({'file': rel_path})
# Emotion
if re.search(r'@emotion|css`', content):
patterns['emotion'].append({'file': rel_path})
# Tailwind (className with utility classes)
if re.search(r'className\s*=\s*["\'][^"\']*(?:flex|grid|p-\d|m-\d|bg-)', content):
patterns['tailwind'].append({'file': rel_path})
# Regular CSS classes
if re.search(r'className\s*=\s*["\'][a-zA-Z]', content):
patterns['css_classes'].append({'file': rel_path})
# Inline styles
for match in INLINE_STYLE_OBJECT.finditer(content):
line = content[:match.start()].count('\n') + 1
patterns['inline_styles'].append({
'file': rel_path,
'line': line,
})
except Exception:
continue
return patterns