NEW SKILL: Design Pattern Learner - Studies and implements web design patterns from external sources - Fetches gists, repositories, and webpages - Analyzes design tokens (colors, typography, spacing, effects) - Extracts UI components (buttons, cards, modals, etc.) - Generates implementation code (Tailwind, React, Vue) Auto-Trigger Patterns: - "study design from [URL]" - "learn from [source]" - "implement this design" - "copy style from" - "extract component from" Integration: - Works alongside ui-ux-pro-max for design guidance - Uses codebase-indexer to find implementation locations - Uses mcp-client for external content fetching - Added to always-use-superpowers decision tree Updated Files: - skills/design-pattern-learner/skill.md - Complete skill documentation - skills/design-pattern-learner/scripts/analyze.py - Pattern analyzer - skills/design-pattern-learner/scripts/generate.py - Implementation generator - skills/design-pattern-learner/README.md - Quick start guide - ralph-integration/dispatch/auto-triggers.yml - Priority 7 - skills/always-use-superpowers/SKILL.md - Decision tree updated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
503 lines
15 KiB
Python
Executable File
503 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Pattern Implementation Generator
|
|
Generates implementation code from analyzed patterns
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import os
|
|
from datetime import datetime
|
|
|
|
class PatternGenerator:
|
|
def __init__(self, pattern_file, framework='tailwind'):
|
|
self.pattern_file = pattern_file
|
|
self.framework = framework
|
|
self.patterns = None
|
|
|
|
def load_patterns(self):
|
|
"""Load pattern JSON file"""
|
|
print(f"📖 Loading patterns from: {self.pattern_file}")
|
|
|
|
try:
|
|
with open(self.pattern_file, 'r') as f:
|
|
self.patterns = json.load(f)
|
|
print(f"✅ Patterns loaded successfully")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Error loading patterns: {e}")
|
|
return False
|
|
|
|
def generate_component_library(self):
|
|
"""Generate component library from patterns"""
|
|
print(f"\n🧩 Generating Component Library for {self.framework}...")
|
|
|
|
if not self.patterns.get('components'):
|
|
print("⚠️ No components found in patterns")
|
|
return {}
|
|
|
|
library = {}
|
|
|
|
for comp in self.patterns['components']:
|
|
comp_type = comp['type']
|
|
examples = comp.get('examples', [])
|
|
|
|
if not examples:
|
|
continue
|
|
|
|
library[comp_type] = {
|
|
'count': comp['count'],
|
|
'implementations': []
|
|
}
|
|
|
|
for i, example in enumerate(examples[:2]): # Max 2 examples
|
|
impl = self._convert_to_framework(comp_type, example)
|
|
if impl:
|
|
library[comp_type]['implementations'].append(impl)
|
|
|
|
print(f" ✓ Generated {comp_type} component")
|
|
|
|
return library
|
|
|
|
def _convert_to_framework(self, comp_type, html_snippet):
|
|
"""Convert HTML snippet to target framework"""
|
|
|
|
# Clean up the snippet
|
|
snippet = html_snippet.strip()
|
|
|
|
if self.framework == 'tailwind':
|
|
return self._convert_tailwind(comp_type, snippet)
|
|
elif self.framework == 'react':
|
|
return self._convert_react(comp_type, snippet)
|
|
elif self.framework == 'vue':
|
|
return self._convert_vue(comp_type, snippet)
|
|
else:
|
|
return {'html': snippet}
|
|
|
|
def _convert_tailwind(self, comp_type, snippet):
|
|
"""Convert to Tailwind HTML"""
|
|
# Extract classes if present
|
|
import re
|
|
|
|
classes = re.findall(r'class="([^"]*)"', snippet)
|
|
all_classes = ' '.join(classes) if classes else ''
|
|
|
|
return {
|
|
'framework': 'tailwind',
|
|
'html': snippet,
|
|
'classes': all_classes,
|
|
'usage': f'<!-- Copy this HTML for your {comp_type} -->'
|
|
}
|
|
|
|
def _convert_react(self, comp_type, snippet):
|
|
"""Convert to React component"""
|
|
import re
|
|
|
|
# Extract classes
|
|
classes = re.findall(r'class="([^"]*)"', snippet)
|
|
|
|
# Convert to JSX
|
|
jsx = snippet
|
|
# Replace class with className
|
|
jsx = re.sub(r'class=', 'className=', jsx)
|
|
# Close self-closing tags
|
|
jsx = re.sub(r'<input([^>]*)>', r'<input\1 />', jsx)
|
|
|
|
component_name = ''.join([w.capitalize() for w in comp_type.split('_')])
|
|
|
|
return {
|
|
'framework': 'react',
|
|
'component_name': component_name,
|
|
'jsx': jsx,
|
|
'code': f'''export function {component_name}() {{
|
|
return (
|
|
{self._indent_jsx(jsx, 4)}
|
|
);
|
|
}}'''
|
|
}
|
|
|
|
def _convert_vue(self, comp_type, snippet):
|
|
"""Convert to Vue component"""
|
|
component_name = ''.join([w.capitalize() for w in comp_type.split('_')])
|
|
|
|
return {
|
|
'framework': 'vue',
|
|
'component_name': component_name,
|
|
'template': snippet,
|
|
'code': f'''<template>
|
|
{self._indent_jsx(snippet, 2)}
|
|
</template>
|
|
|
|
<script setup>
|
|
// {component_name} component
|
|
</script>'''
|
|
}
|
|
|
|
def _indent_jsx(self, code, spaces):
|
|
"""Indent multi-line JSX/Vue code"""
|
|
lines = code.split('\n')
|
|
indent = ' ' * spaces
|
|
return '\n'.join([indent + line if line.strip() else line for line in lines])
|
|
|
|
def generate_design_system(self):
|
|
"""Generate design system from tokens"""
|
|
print(f"\n🎨 Generating Design System...")
|
|
|
|
if not self.patterns.get('design_tokens'):
|
|
print("⚠️ No design tokens found")
|
|
return {}
|
|
|
|
tokens = self.patterns['design_tokens']
|
|
design_system = {}
|
|
|
|
# Colors
|
|
if 'colors' in tokens:
|
|
design_system['colors'] = self._generate_color_tokens(tokens['colors'])
|
|
|
|
# Typography
|
|
if 'typography' in tokens:
|
|
design_system['typography'] = self._generate_typography_tokens(tokens['typography'])
|
|
|
|
# Spacing
|
|
if 'spacing' in tokens:
|
|
design_system['spacing'] = self._generate_spacing_tokens(tokens['spacing'])
|
|
|
|
# Effects
|
|
if 'effects' in tokens:
|
|
design_system['effects'] = self._generate_effect_tokens(tokens['effects'])
|
|
|
|
return design_system
|
|
|
|
def _generate_color_tokens(self, colors):
|
|
"""Generate color tokens"""
|
|
tokens = {'tailwind': {}, 'hex': {}}
|
|
|
|
if 'tailwind' in colors:
|
|
# Group by color name
|
|
for color_class in colors['tailwind']:
|
|
parts = color_class.split('-')
|
|
if len(parts) >= 3:
|
|
prop, color, shade = parts[0], parts[1], parts[2]
|
|
if color not in tokens['tailwind']:
|
|
tokens['tailwind'][color] = []
|
|
tokens['tailwind'][color].append(shade)
|
|
|
|
if 'hex' in colors:
|
|
tokens['hex'] = colors['hex'][:10] # Limit to 10
|
|
|
|
return tokens
|
|
|
|
def _generate_typography_tokens(self, typography):
|
|
"""Generate typography tokens"""
|
|
tokens = {}
|
|
|
|
if 'fonts' in typography:
|
|
tokens['font_families'] = typography['fonts']
|
|
|
|
if 'sizes' in typography:
|
|
tokens['font_sizes'] = typography['sizes']
|
|
|
|
if 'tailwind' in typography:
|
|
tokens['tailwind_classes'] = typography['tailwind']
|
|
|
|
return tokens
|
|
|
|
def _generate_spacing_tokens(self, spacing):
|
|
"""Generate spacing tokens"""
|
|
tokens = {}
|
|
|
|
if 'tailwind' in spacing:
|
|
# Extract unique spacing values
|
|
values = set()
|
|
for class_name in spacing['tailwind']:
|
|
parts = class_name.split('-')
|
|
if len(parts) >= 2:
|
|
values.add(parts[-1])
|
|
tokens['tailwind_scale'] = list(values)
|
|
|
|
if 'css' in spacing:
|
|
tokens['css_values'] = spacing['css'][:5]
|
|
|
|
return tokens
|
|
|
|
def _generate_effect_tokens(self, effects):
|
|
"""Generate effect tokens"""
|
|
tokens = {}
|
|
|
|
if 'shadows' in effects:
|
|
tokens['box_shadows'] = effects['shadows'][:5]
|
|
|
|
if 'tailwind_shadows' in effects:
|
|
tokens['tailwind_shadows'] = effects['tailwind_shadows']
|
|
|
|
if 'radius' in effects:
|
|
tokens['border_radius'] = effects['radius']
|
|
|
|
return tokens
|
|
|
|
def generate_implementation_guide(self):
|
|
"""Generate implementation guide"""
|
|
print(f"\n📖 Generating Implementation Guide...")
|
|
|
|
source = self.patterns.get('source', 'Unknown')
|
|
timestamp = self.patterns.get('timestamp', datetime.now().isoformat())
|
|
|
|
guide = f'''# Design Pattern Implementation Guide
|
|
|
|
**Source:** {source}
|
|
**Analyzed:** {timestamp}
|
|
**Framework:** {self.framework}
|
|
|
|
## Quick Start
|
|
|
|
1. Review the design tokens below
|
|
2. Copy component code for your needs
|
|
3. Integrate into your project
|
|
4. Customize as needed
|
|
|
|
---
|
|
|
|
## Design System
|
|
|
|
### Colors
|
|
{self._format_colors()}
|
|
|
|
### Typography
|
|
{self._format_typography()}
|
|
|
|
### Spacing
|
|
{self._format_spacing()}
|
|
|
|
### Effects
|
|
{self._format_effects()}
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
{self._format_components()}
|
|
|
|
---
|
|
|
|
## Usage Examples
|
|
|
|
### Applying Design Tokens
|
|
|
|
```{self.framework}
|
|
<!-- Example with design tokens -->
|
|
<div class="bg-primary-500 text-white p-4 rounded-lg shadow-lg">
|
|
Content here
|
|
</div>
|
|
```
|
|
|
|
### Using Components
|
|
|
|
Copy any component from the Components section above and adapt to your needs.
|
|
|
|
---
|
|
|
|
## Customization
|
|
|
|
### Modify Colors
|
|
Update your Tailwind config or CSS variables to match the design tokens.
|
|
|
|
### Adjust Spacing
|
|
Scale spacing values up or down based on your design system.
|
|
|
|
### Add Effects
|
|
Apply the shadow and radius patterns to other components.
|
|
|
|
---
|
|
|
|
## Attribution
|
|
|
|
This implementation is based on design patterns from: {source}
|
|
|
|
Please respect the original source's license and attribution requirements.
|
|
|
|
---
|
|
|
|
**Generated by:** Design Pattern Learner v1.0
|
|
**Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
'''
|
|
|
|
return guide
|
|
|
|
def _format_colors(self):
|
|
"""Format colors for guide"""
|
|
if not self.patterns.get('design_tokens', {}).get('colors'):
|
|
return 'No color patterns found'
|
|
|
|
colors = self.patterns['design_tokens']['colors']
|
|
lines = []
|
|
|
|
if 'tailwind' in colors:
|
|
lines.append('\n**Tailwind Colors:**')
|
|
for color in list(colors['tailwind'])[:10]:
|
|
lines.append(f'- `{color}`')
|
|
|
|
if 'hex' in colors:
|
|
lines.append('\n**Hex Colors:**')
|
|
for hex_color in colors['hex'][:10]:
|
|
lines.append(f'- `{hex_color}`')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def _format_typography(self):
|
|
"""Format typography for guide"""
|
|
if not self.patterns.get('design_tokens', {}).get('typography'):
|
|
return 'No typography patterns found'
|
|
|
|
typo = self.patterns['design_tokens']['typography']
|
|
lines = []
|
|
|
|
if 'fonts' in typo:
|
|
lines.append('\n**Font Families:**')
|
|
for font in typo['fonts'][:5]:
|
|
lines.append(f'- `{font}`')
|
|
|
|
if 'tailwind' in typo:
|
|
lines.append('\n**Tailwind Text Classes:**')
|
|
for cls in typo['tailwind'][:10]:
|
|
lines.append(f'- `{cls}`')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def _format_spacing(self):
|
|
"""Format spacing for guide"""
|
|
if not self.patterns.get('design_tokens', {}).get('spacing'):
|
|
return 'No spacing patterns found'
|
|
|
|
spacing = self.patterns['design_tokens']['spacing']
|
|
lines = []
|
|
|
|
if 'tailwind' in spacing:
|
|
lines.append('\n**Tailwind Spacing:**')
|
|
for cls in list(spacing['tailwind'])[:10]:
|
|
lines.append(f'- `{cls}`')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def _format_effects(self):
|
|
"""Format effects for guide"""
|
|
if not self.patterns.get('design_tokens', {}).get('effects'):
|
|
return 'No effect patterns found'
|
|
|
|
effects = self.patterns['design_tokens']['effects']
|
|
lines = []
|
|
|
|
if 'shadows' in effects:
|
|
lines.append('\n**Box Shadows:**')
|
|
for shadow in effects['shadows'][:5]:
|
|
lines.append(f'- `{shadow}`')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def _format_components(self):
|
|
"""Format components for guide"""
|
|
if not self.patterns.get('components'):
|
|
return 'No components found'
|
|
|
|
library = self.generate_component_library()
|
|
lines = []
|
|
|
|
for comp_name, comp_data in library.items():
|
|
lines.append(f'\n### {comp_name.capitalize()}')
|
|
lines.append(f'\nFound: {comp_data["count"]} instance(s)')
|
|
|
|
for impl in comp_data.get('implementations', [])[:2]:
|
|
if self.framework == 'tailwind':
|
|
lines.append(f'\n```html')
|
|
lines.append(impl.get('html', ''))
|
|
lines.append('```')
|
|
elif self.framework == 'react':
|
|
lines.append(f'\n```jsx')
|
|
lines.append(impl.get('code', ''))
|
|
lines.append('```')
|
|
elif self.framework == 'vue':
|
|
lines.append(f'\n```vue')
|
|
lines.append(impl.get('code', ''))
|
|
lines.append('```')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def save_outputs(self, library, design_system, guide):
|
|
"""Save generated outputs"""
|
|
output_dir = os.path.expanduser('~/.claude/skills/design-pattern-learner/data/outputs')
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
|
|
# Save component library
|
|
library_file = os.path.join(output_dir, f'library_{self.framework}_{timestamp}.json')
|
|
with open(library_file, 'w') as f:
|
|
json.dump(library, f, indent=2)
|
|
print(f'\n💾 Component library: {library_file}')
|
|
|
|
# Save design system
|
|
system_file = os.path.join(output_dir, f'design-system_{self.framework}_{timestamp}.json')
|
|
with open(system_file, 'w') as f:
|
|
json.dump(design_system, f, indent=2)
|
|
print(f'💾 Design system: {system_file}')
|
|
|
|
# Save implementation guide
|
|
guide_file = os.path.join(output_dir, f'implementation-guide_{self.framework}_{timestamp}.md')
|
|
with open(guide_file, 'w') as f:
|
|
f.write(guide)
|
|
print(f'💾 Implementation guide: {guide_file}')
|
|
|
|
return library_file, system_file, guide_file
|
|
|
|
def main():
|
|
if len(sys.argv) < 3:
|
|
print("Usage: python3 generate.py --source <pattern_file> --framework <tailwind|react|vue>")
|
|
print("Example: python3 generate.py --source patterns.json --framework tailwind")
|
|
sys.exit(1)
|
|
|
|
pattern_file = None
|
|
framework = 'tailwind'
|
|
|
|
args = sys.argv[1:]
|
|
i = 0
|
|
while i < len(args):
|
|
if args[i] == '--source' and i + 1 < len(args):
|
|
pattern_file = args[i + 1]
|
|
i += 2
|
|
elif args[i] == '--framework' and i + 1 < len(args):
|
|
framework = args[i + 1].lower()
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
|
|
if not pattern_file:
|
|
print("❌ Error: --source parameter is required")
|
|
sys.exit(1)
|
|
|
|
if framework not in ['tailwind', 'react', 'vue']:
|
|
print(f"⚠️ Warning: Unknown framework '{framework}', using 'tailwind'")
|
|
framework = 'tailwind'
|
|
|
|
generator = PatternGenerator(pattern_file, framework)
|
|
|
|
if not generator.load_patterns():
|
|
sys.exit(1)
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"🚀 GENERATING IMPLEMENTATION FOR {framework.upper()}")
|
|
print(f"{'='*60}")
|
|
|
|
# Generate outputs
|
|
library = generator.generate_component_library()
|
|
design_system = generator.generate_design_system()
|
|
guide = generator.generate_implementation_guide()
|
|
|
|
# Save outputs
|
|
generator.save_outputs(library, design_system, guide)
|
|
|
|
print(f"\n{'='*60}")
|
|
print("✅ GENERATION COMPLETE!")
|
|
print(f"{'='*60}")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|