Skip to main content
Glama
srwlli

Documentation Generator MCP Server

by srwlli
standards_generator.py46.7 kB
""" Standards generator for establish_standards tool. Scans codebase to discover UI/UX/behavior patterns and generates comprehensive standards documentation. """ import re from pathlib import Path from typing import List, Dict from datetime import datetime from constants import ( Paths, Files, ScanDepth, FocusArea, EXCLUDE_DIRS, MAX_FILE_SIZE, ALLOWED_FILE_EXTENSIONS ) from type_defs import ( UIPatternDict, BehaviorPatternDict, UXPatternDict, ComponentMetadataDict, StandardsResultDict ) from logger_config import logger, log_security_event class StandardsGenerator: """ Generator for discovering code patterns and creating standards documentation. Scans project files to find UI components, behavior patterns, and UX flows, then generates markdown documentation of discovered standards. """ def __init__(self, project_path: Path, scan_depth: str = ScanDepth.STANDARD.value): """ Initialize standards generator. Args: project_path: Absolute path to project directory scan_depth: Analysis depth ('quick', 'standard', or 'deep') """ self.project_path = project_path.resolve() # SEC-001: Canonicalize path self.scan_depth = scan_depth # Compile regex patterns once for performance self._button_pattern = re.compile(r'<Button[^>]*>', re.DOTALL) self._modal_pattern = re.compile(r'<Modal[^>]*>|<Dialog[^>]*>', re.DOTALL) self._color_pattern = re.compile(r'#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}') self._error_pattern = re.compile(r'throw new Error\([\'"](.+?)[\'"]\)|toast\.error\([\'"](.+?)[\'"]\)') self._loading_pattern = re.compile(r'isLoading|loading\s*[:=]|<Spinner|<Loading') # Theme system detection patterns self._theme_hook_pattern = re.compile(r'const\s+{\s*\w+\s*}\s*=\s*use\w*[Tt]heme\w*\(\)') self._color_scheme_interface = re.compile(r'interface\s+ColorScheme\s*{([^}]+)}', re.DOTALL) self._theme_export = re.compile(r'export\s+const\s+Themes\s*[:=]', re.MULTILINE) logger.debug(f"Initialized StandardsGenerator for {project_path} with depth={scan_depth}") def scan_codebase(self) -> Dict[str, List[Path]]: """ Scan all source files and collect code samples. Returns: Dict with files grouped by type (tsx, css, etc.) """ logger.info("Starting codebase scan", extra={'scan_depth': self.scan_depth}) files_by_type: Dict[str, List[Path]] = { 'tsx': [], 'jsx': [], 'ts': [], 'js': [], 'css': [] } # SEC-004: Canonicalize excluded paths excluded_paths = [Path(self.project_path / d).resolve() for d in EXCLUDE_DIRS] # Scan for all relevant files for ext in ALLOWED_FILE_EXTENSIONS: pattern = f"**/*{ext}" for file_path in self.project_path.glob(pattern): file_resolved = file_path.resolve() # SEC-004: Canonicalize file path # Check if file is under any excluded directory if any(file_resolved.is_relative_to(exc_path) for exc_path in excluded_paths if exc_path.exists()): logger.debug(f"Skipping excluded file: {file_path}", extra={'reason': 'excluded_directory'}) continue # SEC-008: Check for symlinks if file_path.is_symlink(): if not file_resolved.is_relative_to(self.project_path.resolve()): log_security_event( 'symlink_outside_project', f"Skipping symlink pointing outside project: {file_path} -> {file_resolved}", file_path=str(file_path), target=str(file_resolved) ) continue # SEC-007: Check file size try: file_size = file_path.stat().st_size if file_size > MAX_FILE_SIZE: logger.warning( f"Skipping large file: {file_path}", extra={'file_size_mb': file_size / 1024 / 1024, 'max_size_mb': MAX_FILE_SIZE / 1024 / 1024} ) continue except OSError: continue # Add to appropriate category ext_clean = ext.lstrip('.') if ext_clean in files_by_type: files_by_type[ext_clean].append(file_path) total_files = sum(len(files) for files in files_by_type.values()) logger.info(f"Scan completed: found {total_files} source files", extra={'files_by_type': {k: len(v) for k, v in files_by_type.items()}}) return files_by_type def detect_theme_system(self, files: Dict[str, List[Path]]) -> Dict: """ Detect centralized theme system by looking for theme files and hooks. Args: files: Dict with files grouped by type Returns: Dict with theme system details or None if not found """ logger.info("Detecting theme system") theme_system = { 'detected': False, 'theme_file': None, 'hook_file': None, 'hook_name': None, 'colors': [], 'theme_names': [], 'color_count': 0 } # Search for theme files in common locations theme_file_patterns = [ 'constants/theme.ts', 'constants/theme.js', 'constants/colors.ts', 'constants/colors.js', 'theme/colors.ts', 'theme/colors.js', 'theme/index.ts', 'theme/index.js', 'config/theme.ts', 'config/theme.js' ] for pattern in theme_file_patterns: theme_path = self.project_path / pattern if theme_path.exists(): theme_system['theme_file'] = theme_path logger.info(f"Found theme file: {pattern}") break # Search for theme hook files hook_file_patterns = [ 'hooks/use-theme-colors.ts', 'hooks/use-theme-colors.tsx', 'hooks/useThemeColors.ts', 'hooks/useThemeColors.tsx', 'hooks/use-theme.ts', 'hooks/use-theme.tsx', 'hooks/useTheme.ts', 'hooks/useTheme.tsx' ] for pattern in hook_file_patterns: hook_path = self.project_path / pattern if hook_path.exists(): theme_system['hook_file'] = hook_path logger.info(f"Found theme hook: {pattern}") break # If we found a theme file, parse its structure if theme_system['theme_file']: parsed = self.parse_theme_structure(theme_system['theme_file'], theme_system.get('hook_file')) if parsed['is_valid']: theme_system['detected'] = True theme_system['colors'] = parsed['colors'] theme_system['theme_names'] = parsed['theme_names'] theme_system['color_count'] = len(parsed['colors']) theme_system['hook_name'] = parsed['hook_name'] logger.info(f"Theme system detected: {theme_system['color_count']} colors, {len(theme_system['theme_names'])} themes") return theme_system def parse_theme_structure(self, theme_file: Path, hook_file: Path = None) -> Dict: """ Parse theme file to extract color scheme and theme names. Args: theme_file: Path to theme constants file hook_file: Optional path to theme hook file Returns: Dict with parsed theme structure """ logger.debug(f"Parsing theme structure from {theme_file}") result = { 'is_valid': False, 'colors': [], 'theme_names': [], 'hook_name': None } try: content = theme_file.read_text(encoding='utf-8') # Extract ColorScheme interface (handle nested braces properly) interface_start = content.find('interface ColorScheme') if interface_start != -1: # Find the opening brace open_brace = content.find('{', interface_start) if open_brace != -1: # Count braces to find matching close brace_count = 1 pos = open_brace + 1 while pos < len(content) and brace_count > 0: if content[pos] == '{': brace_count += 1 elif content[pos] == '}': brace_count -= 1 pos += 1 if brace_count == 0: interface_body = content[open_brace+1:pos-1] # Extract property names (color names) property_pattern = re.compile(r'(\w+)\s*:\s*string') colors = property_pattern.findall(interface_body) result['colors'] = colors logger.debug(f"Found {len(colors)} color properties") # Extract theme names from ThemeName type or Themes object # Pattern 1: type ThemeName = 'foo' | 'bar' | 'baz'; theme_type_pattern = re.compile(r"type\s+ThemeName\s*=\s*([^;]+);", re.MULTILINE) theme_type_match = theme_type_pattern.search(content) if theme_type_match: theme_string = theme_type_match.group(1) # Extract quoted strings theme_names = re.findall(r"['\"](\w+)['\"]", theme_string) result['theme_names'] = theme_names logger.debug(f"Found {len(theme_names)} theme names from type definition") # Pattern 2: export const Themes = { foo: {...}, bar: {...} } if not result['theme_names'] and self._theme_export.search(content): # Find theme names from object keys themes_object_pattern = re.compile(r'Themes\s*[:=]\s*\{([^}]+)\}', re.DOTALL) themes_match = themes_object_pattern.search(content) if themes_match: themes_body = themes_match.group(1) # Extract keys key_pattern = re.compile(r'(\w+)\s*:') theme_names = key_pattern.findall(themes_body) result['theme_names'] = theme_names logger.debug(f"Found {len(theme_names)} theme names from Themes object") # Extract hook name from hook file if hook_file and hook_file.exists(): hook_content = hook_file.read_text(encoding='utf-8') # Pattern: export function useThemeColors() hook_pattern = re.compile(r'export\s+function\s+(use\w+)\s*\(') hook_match = hook_pattern.search(hook_content) if hook_match: result['hook_name'] = hook_match.group(1) logger.debug(f"Found hook name: {result['hook_name']}") # Validate that we found essential components if result['colors'] and result['theme_names']: result['is_valid'] = True except Exception as e: logger.debug(f"Error parsing theme structure: {e}") return result def analyze_ui_patterns(self, files: Dict[str, List[Path]]) -> UIPatternDict: """ Analyze UI component patterns from source files. Args: files: Dict with files grouped by type Returns: UIPatternDict with discovered UI patterns """ logger.info("Analyzing UI patterns") ui_patterns: UIPatternDict = { 'buttons': {'sizes': [], 'variants': [], 'common_props': []}, 'modals': {'sizes': [], 'positions': [], 'backdrop': []}, 'forms': {'input_types': [], 'validation': [], 'error_states': []}, 'colors': {'hex_codes': [], 'css_vars': [], 'theme_colors': []}, 'typography': {'font_sizes': [], 'weights': [], 'families': []}, 'spacing': {'margins': [], 'paddings': [], 'gaps': []}, 'icons': {'library': 'unknown', 'sizes': [], 'colors': []} } # Detect theme system first theme_system = self.detect_theme_system(files) if theme_system['detected']: ui_patterns['theme_system'] = theme_system # Track component library usage shadcn_components = set() tailwind_patterns = set() # Analyze TSX/JSX files for UI components ui_files = files.get('tsx', []) + files.get('jsx', []) for file_path in ui_files: try: content = file_path.read_text(encoding='utf-8') # Find button patterns button_matches = self._button_pattern.findall(content) for match in button_matches: # Extract size prop size_match = re.search(r'size=[\'"](\w+)[\'"]', match) if size_match and size_match.group(1) not in ui_patterns['buttons']['sizes']: ui_patterns['buttons']['sizes'].append(size_match.group(1)) # Extract variant prop variant_match = re.search(r'variant=[\'"](\w+)[\'"]', match) if variant_match and variant_match.group(1) not in ui_patterns['buttons']['variants']: ui_patterns['buttons']['variants'].append(variant_match.group(1)) # Find modal/dialog patterns modal_matches = self._modal_pattern.findall(content) for match in modal_matches: size_match = re.search(r'size=[\'"](\w+)[\'"]', match) if size_match and size_match.group(1) not in ui_patterns['modals']['sizes']: ui_patterns['modals']['sizes'].append(size_match.group(1)) # Find color usage color_matches = self._color_pattern.findall(content) for color in color_matches: if color not in ui_patterns['colors']['hex_codes']: ui_patterns['colors']['hex_codes'].append(color) # If theme system detected, track violations if theme_system['detected'] and color_matches: if 'hardcoded_violations' not in ui_patterns: ui_patterns['hardcoded_violations'] = { 'count': 0, 'files': [], 'common_colors': {} } ui_patterns['hardcoded_violations']['count'] += len(color_matches) file_rel_path = str(file_path.relative_to(self.project_path)) if file_rel_path not in ui_patterns['hardcoded_violations']['files']: ui_patterns['hardcoded_violations']['files'].append(file_rel_path) # Count common violations for color in color_matches: if color not in ui_patterns['hardcoded_violations']['common_colors']: ui_patterns['hardcoded_violations']['common_colors'][color] = 0 ui_patterns['hardcoded_violations']['common_colors'][color] += 1 # Detect shadcn/ui component imports shadcn_import_pattern = re.findall(r'from [\'"]@/components/ui/(\w+)[\'"]', content) shadcn_components.update(shadcn_import_pattern) # Detect Tailwind patterns (common utility classes) tailwind_classes = re.findall(r'className=[\'"]([^\'"]+)[\'"]', content) for class_str in tailwind_classes: # Extract common patterns classes = class_str.split() for cls in classes: # Track common patterns if cls.startswith('flex'): tailwind_patterns.add('flex-layout') elif cls.startswith('grid'): tailwind_patterns.add('grid-layout') elif 'gap-' in cls: tailwind_patterns.add(cls) elif cls.startswith('p-') or cls.startswith('px-') or cls.startswith('py-'): tailwind_patterns.add('padding-utilities') elif cls.startswith('m-') or cls.startswith('mx-') or cls.startswith('my-'): tailwind_patterns.add('margin-utilities') # Detect icon library usage if 'lucide-react' in content: ui_patterns['icons']['library'] = 'lucide-react' elif 'react-icons' in content: ui_patterns['icons']['library'] = 'react-icons' elif '@heroicons' in content: ui_patterns['icons']['library'] = 'heroicons' except Exception as e: logger.debug(f"Error analyzing UI patterns in {file_path}: {e}") continue # Store discovered shadcn/ui components if shadcn_components: ui_patterns['shadcn_components'] = sorted(list(shadcn_components)) # Store discovered Tailwind patterns if tailwind_patterns: ui_patterns['tailwind_patterns'] = sorted(list(tailwind_patterns)) patterns_count = len(ui_patterns['buttons']['sizes']) + len(ui_patterns['modals']['sizes']) + len(ui_patterns['colors']['hex_codes']) logger.info(f"UI pattern analysis complete: found {patterns_count} patterns") return ui_patterns def analyze_behavior_patterns(self, files: Dict[str, List[Path]]) -> BehaviorPatternDict: """ Analyze behavior patterns (errors, loading, etc.). Args: files: Dict with files grouped by type Returns: BehaviorPatternDict with behavior patterns and examples """ logger.info("Analyzing behavior patterns") behavior_patterns: BehaviorPatternDict = { 'error_handling': {'patterns': [], 'messages': [], 'recovery': []}, 'loading_states': {'indicators': [], 'skeleton': [], 'flags': []}, 'toasts': {'duration': [], 'position': [], 'types': []}, 'validation': {'rules': [], 'timing': [], 'messages': []}, 'api_communication': {'error_handling': [], 'retries': []} } # Track state management patterns state_management_patterns = set() client_component_patterns = set() server_component_patterns = set() # Analyze all code files code_files = files.get('tsx', []) + files.get('jsx', []) + files.get('ts', []) + files.get('js', []) for file_path in code_files: try: content = file_path.read_text(encoding='utf-8') # Find error handling patterns error_matches = self._error_pattern.findall(content) for match in error_matches: message = match[0] or match[1] # Get whichever group matched if message and message not in behavior_patterns['error_handling']['messages']: behavior_patterns['error_handling']['messages'].append(message) # Find loading state patterns if self._loading_pattern.search(content): if 'loading_state_detected' not in behavior_patterns['loading_states']['indicators']: behavior_patterns['loading_states']['indicators'].append('loading_state_detected') # Detect ErrorBoundary usage if 'ErrorBoundary' in content: if 'error_boundary' not in behavior_patterns['error_handling']['patterns']: behavior_patterns['error_handling']['patterns'].append('error_boundary') # Detect try-catch patterns if re.search(r'try\s*{', content): if 'try_catch' not in behavior_patterns['error_handling']['patterns']: behavior_patterns['error_handling']['patterns'].append('try_catch') # Detect toast/notification libraries if 'toast' in content or 'sonner' in content: if 'toast_notifications' not in behavior_patterns['toasts']['types']: behavior_patterns['toasts']['types'].append('toast_notifications') # Detect state management patterns if 'useState' in content: state_management_patterns.add('useState') if 'useReducer' in content: state_management_patterns.add('useReducer') if 'createContext' in content or 'useContext' in content: state_management_patterns.add('Context_API') if 'zustand' in content or 'create(' in content: state_management_patterns.add('Zustand') # Detect client vs server components if '"use client"' in content or "'use client'" in content: client_component_patterns.add(str(file_path.relative_to(self.project_path))) elif file_path.suffix in ['.tsx', '.ts'] and 'export default' in content and '"use client"' not in content: # Likely a server component (no "use client" directive) server_component_patterns.add(str(file_path.relative_to(self.project_path))) # Detect data fetching patterns if 'fetch(' in content: if 'fetch_api' not in behavior_patterns['api_communication']['patterns']: behavior_patterns['api_communication']['patterns'] = ['fetch_api'] if 'axios' in content: if 'axios' not in behavior_patterns['api_communication']['patterns']: if 'patterns' not in behavior_patterns['api_communication']: behavior_patterns['api_communication']['patterns'] = [] behavior_patterns['api_communication']['patterns'].append('axios') except Exception as e: logger.debug(f"Error analyzing behavior patterns in {file_path}: {e}") continue # Store state management patterns if state_management_patterns: behavior_patterns['state_management'] = { 'patterns': sorted(list(state_management_patterns)) } # Store component type patterns if client_component_patterns or server_component_patterns: behavior_patterns['component_types'] = { 'client_components_count': len(client_component_patterns), 'server_components_count': len(server_component_patterns), 'uses_client_directive': len(client_component_patterns) > 0 } patterns_count = len(behavior_patterns['error_handling']['messages']) + len(behavior_patterns['loading_states']['indicators']) logger.info(f"Behavior pattern analysis complete: found {patterns_count} patterns") return behavior_patterns def analyze_ux_patterns(self, files: Dict[str, List[Path]]) -> UXPatternDict: """ Analyze UX flow patterns (navigation, permissions). Args: files: Dict with files grouped by type Returns: UXPatternDict with UX patterns and usage """ logger.info("Analyzing UX patterns") ux_patterns: UXPatternDict = { 'navigation': {'routing': [], 'breadcrumbs': [], 'back_buttons': []}, 'permissions': {'auth_guards': [], 'role_checks': [], 'fallbacks': []}, 'offline_handling': {'detection': [], 'fallbacks': [], 'sync': []}, 'accessibility': {'aria': [], 'keyboard': [], 'screen_readers': []} } # Track Next.js specific patterns nextjs_patterns = set() file_organization = { 'app_router_routes': [], 'route_groups': [], 'dynamic_routes': [], 'component_dirs': set(), 'layout_files': [] } # Analyze TSX/JSX files for UX patterns ui_files = files.get('tsx', []) + files.get('jsx', []) # Detect Next.js app router structure app_dir = self.project_path / 'app' / '(app)' if app_dir.exists(): nextjs_patterns.add('app_router') # Find routes for route_dir in app_dir.iterdir(): if route_dir.is_dir(): file_organization['app_router_routes'].append(str(route_dir.name)) # Check for route groups (folders with parentheses) for file_path in ui_files: rel_path = file_path.relative_to(self.project_path) path_str = str(rel_path) # Detect route groups if '(' in path_str and ')' in path_str: nextjs_patterns.add('route_groups') # Detect dynamic routes if '[' in path_str and ']' in path_str: nextjs_patterns.add('dynamic_routes') # Track layout files if file_path.name == 'layout.tsx': file_organization['layout_files'].append(str(rel_path)) # Track component organization if 'components' in path_str: parts = Path(path_str).parts if 'components' in parts: idx = parts.index('components') if idx + 1 < len(parts): file_organization['component_dirs'].add(parts[idx + 1]) for file_path in ui_files: try: content = file_path.read_text(encoding='utf-8') # Find navigation patterns if 'useRouter' in content: if 'next_router' not in ux_patterns['navigation']['routing']: ux_patterns['navigation']['routing'].append('next_router') elif 'useNavigate' in content: if 'react_router' not in ux_patterns['navigation']['routing']: ux_patterns['navigation']['routing'].append('react_router') # Detect Link components if '<Link' in content and 'next/link' in content: nextjs_patterns.add('next_link') # Find accessibility patterns aria_match = re.search(r'aria-\w+', content) if aria_match and 'aria_attributes_detected' not in ux_patterns['accessibility']['aria']: ux_patterns['accessibility']['aria'].append('aria_attributes_detected') # Detect authentication patterns if 'useAuth' in content or 'AuthProvider' in content: if 'auth_context' not in ux_patterns['permissions']['auth_guards']: ux_patterns['permissions']['auth_guards'].append('auth_context') # Detect Supabase authentication if 'supabase' in content.lower() and ('signIn' in content or 'signOut' in content or 'auth' in content): if 'supabase_auth' not in ux_patterns['permissions']['auth_guards']: ux_patterns['permissions']['auth_guards'].append('supabase_auth') except Exception as e: logger.debug(f"Error analyzing UX patterns in {file_path}: {e}") continue # Store Next.js patterns if nextjs_patterns: ux_patterns['nextjs_features'] = sorted(list(nextjs_patterns)) # Store file organization patterns if file_organization['app_router_routes'] or file_organization['component_dirs']: ux_patterns['file_organization'] = { 'routes': file_organization['app_router_routes'], 'component_directories': sorted(list(file_organization['component_dirs'])), 'layout_count': len(file_organization['layout_files']) } patterns_count = len(ux_patterns['navigation']['routing']) + len(ux_patterns['accessibility']['aria']) logger.info(f"UX pattern analysis complete: found {patterns_count} patterns") return ux_patterns def build_component_index(self, files: Dict[str, List[Path]]) -> List[ComponentMetadataDict]: """ Build comprehensive component inventory. Args: files: Dict with files grouped by type Returns: List of ComponentMetadataDict with component metadata """ logger.info("Building component index") components: List[ComponentMetadataDict] = [] # Analyze TSX/JSX files for component definitions ui_files = files.get('tsx', []) + files.get('jsx', []) # Pattern to find React component declarations component_pattern = re.compile(r'(?:export\s+(?:default\s+)?)?(?:function|const)\s+(\w+)\s*[=:]') for file_path in ui_files: try: content = file_path.read_text(encoding='utf-8') matches = component_pattern.findall(content) for component_name in matches: # Skip non-component functions (lowercase first letter) if not component_name[0].isupper(): continue component: ComponentMetadataDict = { 'name': component_name, 'type': 'ui', 'usage_count': 0, # Would need full analysis to determine 'status': 'active', 'props': [], # Would need AST parsing for accurate extraction 'file_path': str(file_path.relative_to(self.project_path)), 'notes': f'Discovered in {file_path.name}' } components.append(component) except Exception as e: logger.debug(f"Error building component index for {file_path}: {e}") continue logger.info(f"Component index complete: found {len(components)} components") return components def generate_ui_standards_doc(self, patterns: UIPatternDict) -> str: """ Generate UI-STANDARDS.md content from discovered UI patterns. Args: patterns: UIPatternDict with UI patterns Returns: Markdown formatted standards document """ project_name = self.project_path.name date = datetime.now().strftime('%Y-%m-%d') doc = f"""# UI Standards **Generated**: {date} **Project**: {project_name} **Pattern Discovery**: {self.scan_depth} --- ## Overview This document defines the UI standards discovered in the {project_name} codebase through automated pattern analysis. """ # Component Library section if patterns.get('shadcn_components'): doc += "## Component Library\n\n" doc += "**Framework**: shadcn/ui\n\n" doc += "**Components in Use**:\n" for comp in patterns['shadcn_components']: doc += f"- `{comp}`\n" doc += "\n" # Icons section if patterns['icons']['library'] != 'unknown': doc += "## Icons\n\n" doc += f"**Library**: {patterns['icons']['library']}\n\n" # Buttons section doc += "## Buttons\n\n" if patterns['buttons']['sizes']: doc += f"**Discovered Sizes**: {', '.join(patterns['buttons']['sizes'])}\n\n" if patterns['buttons']['variants']: doc += f"**Discovered Variants**: {', '.join(patterns['buttons']['variants'])}\n\n" if not patterns['buttons']['sizes'] and not patterns['buttons']['variants']: doc += "*No button patterns discovered*\n\n" # Modals section doc += "## Modals & Dialogs\n\n" if patterns['modals']['sizes']: doc += f"**Discovered Sizes**: {', '.join(patterns['modals']['sizes'])}\n\n" else: doc += "*No modal patterns discovered*\n\n" # Styling section if patterns.get('tailwind_patterns'): doc += "## Styling\n\n" doc += "**Framework**: Tailwind CSS\n\n" doc += "**Common Patterns**:\n" for pattern in patterns['tailwind_patterns']: doc += f"- `{pattern}`\n" doc += "\n" # Colors section doc += "## Colors\n\n" # Check if theme system was detected if patterns.get('theme_system') and patterns['theme_system']['detected']: theme = patterns['theme_system'] theme_file_rel = str(theme['theme_file'].relative_to(self.project_path)) if theme['theme_file'] else 'unknown' doc += f"**Theme System**: {theme['color_count']}-Color Palette (see `{theme_file_rel}` for complete specification)\n\n" # Core theme colors doc += f"### Core Theme Colors ({theme['color_count']} colors)\n" if theme['hook_name']: doc += f"All components MUST use theme colors via `{theme['hook_name']}` hook - never hardcoded hex values.\n\n" else: doc += "All components MUST use theme colors - never hardcoded hex values.\n\n" doc += "```typescript\n" if theme['hook_name']: doc += f"const {{ colors }} = {theme['hook_name']}();\n\n" else: doc += "const { colors } = useThemeColors();\n\n" # List all color properties doc += "// Available colors\n" for color_name in theme['colors']: doc += f"colors.{color_name}\n" doc += "```\n\n" # Available themes doc += f"### Available Themes ({len(theme['theme_names'])} themes)\n" doc += ", ".join(theme['theme_names']) + "\n\n" doc += f"Each theme provides {theme['color_count']} color values (light + dark modes).\n\n" # Usage pattern doc += "### Usage Pattern\n\n" doc += "**ALWAYS use theme colors:**\n\n" doc += "```tsx\n" doc += "// ✅ CORRECT\n" doc += "<View style={{ backgroundColor: colors.surface }}>\n" doc += " <Text style={{ color: colors.text }}>Hello</Text>\n" doc += "</View>\n\n" doc += "// ❌ WRONG - Never hardcode colors\n" doc += "<View style={{ backgroundColor: '#ffffff' }}>\n" doc += " <Text style={{ color: '#000000' }}>Hello</Text>\n" doc += "</View>\n" doc += "```\n\n" # Anti-patterns found if patterns.get('hardcoded_violations'): violations = patterns['hardcoded_violations'] doc += "### Anti-patterns Found\n\n" doc += f"⚠️ **{violations['count']} hardcoded hex colors detected in {len(violations['files'])} files**\n\n" # Show top 3 common violations if violations['common_colors']: sorted_colors = sorted(violations['common_colors'].items(), key=lambda x: x[1], reverse=True) doc += "**Common violations:**\n" for color, count in sorted_colors[:3]: doc += f"- `{color}` ({count} occurrences)\n" doc += "\n" doc += f"**Reference:** `{theme_file_rel}` for complete theme definitions\n\n" else: # Fallback to hardcoded color discovery if patterns['colors']['hex_codes']: doc += "**Discovered Colors**:\n" for color in patterns['colors']['hex_codes'][:20]: # Limit to first 20 doc += f"- `{color}`\n" if len(patterns['colors']['hex_codes']) > 20: doc += f"\n*...and {len(patterns['colors']['hex_codes']) - 20} more*\n" doc += "\n" else: doc += "*No color patterns discovered*\n\n" doc += "---\n\n*Generated by docs-mcp establish_standards tool*\n" return doc def generate_behavior_standards_doc(self, patterns: BehaviorPatternDict) -> str: """ Generate BEHAVIOR-STANDARDS.md content from discovered behavior patterns. Args: patterns: BehaviorPatternDict with behavior patterns Returns: Markdown formatted standards document """ project_name = self.project_path.name date = datetime.now().strftime('%Y-%m-%d') doc = f"""# Behavior Standards **Generated**: {date} **Project**: {project_name} **Pattern Discovery**: {self.scan_depth} --- ## Overview This document defines the behavior standards discovered in the {project_name} codebase. """ # Component Types section if patterns.get('component_types'): doc += "## Component Architecture\n\n" ct = patterns['component_types'] doc += f"**Server Components**: {ct['server_components_count']}\n" doc += f"**Client Components**: {ct['client_components_count']}\n" if ct['uses_client_directive']: doc += f"\n**Pattern**: Uses `'use client'` directive for client-side components\n" doc += "\n" # State Management section if patterns.get('state_management'): doc += "## State Management\n\n" doc += "**Patterns in Use**:\n" for pattern in patterns['state_management']['patterns']: doc += f"- {pattern}\n" doc += "\n" # Error Handling section doc += "## Error Handling\n\n" if patterns['error_handling']['patterns']: doc += "**Patterns**:\n" for pattern in patterns['error_handling']['patterns']: doc += f"- {pattern}\n" doc += "\n" if patterns['error_handling']['messages']: doc += "**Common Error Messages**:\n\n" for msg in patterns['error_handling']['messages'][:10]: doc += f"- `{msg}`\n" if len(patterns['error_handling']['messages']) > 10: doc += f"\n*...and {len(patterns['error_handling']['messages']) - 10} more*\n" doc += "\n" if not patterns['error_handling']['patterns'] and not patterns['error_handling']['messages']: doc += "*No error handling patterns discovered*\n\n" # Notifications section if patterns['toasts']['types']: doc += "## Notifications\n\n" doc += "**Toast Library**: Detected\n" doc += "**Types**: " + ", ".join(patterns['toasts']['types']) + "\n\n" # Loading States section doc += "## Loading States\n\n" if patterns['loading_states']['indicators']: doc += "*Loading state patterns detected in codebase*\n\n" else: doc += "*No loading state patterns discovered*\n\n" # API Communication section if patterns.get('api_communication') and patterns['api_communication'].get('patterns'): doc += "## API Communication\n\n" doc += "**Patterns**:\n" for pattern in patterns['api_communication']['patterns']: doc += f"- {pattern}\n" doc += "\n" doc += "---\n\n*Generated by docs-mcp establish_standards tool*\n" return doc def generate_ux_patterns_doc(self, patterns: UXPatternDict) -> str: """ Generate UX-PATTERNS.md content from discovered UX patterns. Args: patterns: UXPatternDict with UX patterns Returns: Markdown formatted patterns document """ project_name = self.project_path.name date = datetime.now().strftime('%Y-%m-%d') doc = f"""# UX Patterns **Generated**: {date} **Project**: {project_name} **Pattern Discovery**: {self.scan_depth} --- ## Overview This document defines the UX patterns discovered in the {project_name} codebase. """ # Next.js Features section if patterns.get('nextjs_features'): doc += "## Next.js Features\n\n" doc += "**Detected Features**:\n" for feature in patterns['nextjs_features']: doc += f"- {feature}\n" doc += "\n" # File Organization section if patterns.get('file_organization'): doc += "## File Organization\n\n" fo = patterns['file_organization'] if fo['routes']: doc += "**App Router Routes**:\n" for route in fo['routes']: doc += f"- `/{route}`\n" doc += "\n" if fo['component_directories']: doc += "**Component Directories**:\n" for dir_name in fo['component_directories']: doc += f"- `components/{dir_name}/`\n" doc += "\n" if fo['layout_count'] > 0: doc += f"**Layout Files**: {fo['layout_count']} layouts detected\n\n" # Navigation section doc += "## Navigation\n\n" if patterns['navigation']['routing']: doc += "**Routing Pattern**: " + ", ".join(patterns['navigation']['routing']) + "\n\n" else: doc += "*No navigation patterns discovered*\n\n" # Authentication & Permissions section if patterns['permissions']['auth_guards']: doc += "## Authentication & Permissions\n\n" doc += "**Authentication Patterns**:\n" for pattern in patterns['permissions']['auth_guards']: doc += f"- {pattern}\n" doc += "\n" # Accessibility section doc += "## Accessibility\n\n" if patterns['accessibility']['aria']: doc += "**Accessibility Features**:\n" for feature in patterns['accessibility']['aria']: doc += f"- {feature}\n" doc += "\n" else: doc += "*No accessibility patterns discovered*\n\n" doc += "---\n\n*Generated by docs-mcp establish_standards tool*\n" return doc def generate_component_index_doc(self, components: List[ComponentMetadataDict]) -> str: """ Generate COMPONENT-INDEX.md content from component inventory. Args: components: List of ComponentMetadataDict Returns: Markdown formatted component inventory """ project_name = self.project_path.name date = datetime.now().strftime('%Y-%m-%d') doc = f"""# Component Index **Generated**: {date} **Project**: {project_name} **Total Components**: {len(components)} --- ## Component Inventory """ if components: doc += "| Component | Type | Status | File Path |\n" doc += "|-----------|------|--------|----------|\n" for comp in components: doc += f"| {comp['name']} | {comp['type']} | {comp['status']} | {comp['file_path']} |\n" else: doc += "*No components discovered*\n" doc += "\n---\n\n*Generated by docs-mcp establish_standards tool*\n" return doc def save_standards(self, standards_dir: Path) -> StandardsResultDict: """ Save all generated standards documents to disk. Args: standards_dir: Directory to save standards documents Returns: StandardsResultDict with save results """ logger.info(f"Saving standards to {standards_dir}") # Ensure directory exists standards_dir.mkdir(parents=True, exist_ok=True) # Scan codebase files = self.scan_codebase() # Analyze patterns ui_patterns = self.analyze_ui_patterns(files) behavior_patterns = self.analyze_behavior_patterns(files) ux_patterns = self.analyze_ux_patterns(files) components = self.build_component_index(files) # Generate documents ui_doc = self.generate_ui_standards_doc(ui_patterns) behavior_doc = self.generate_behavior_standards_doc(behavior_patterns) ux_doc = self.generate_ux_patterns_doc(ux_patterns) component_doc = self.generate_component_index_doc(components) # Save files saved_files = [] ui_path = standards_dir / Files.UI_STANDARDS ui_path.write_text(ui_doc, encoding='utf-8') saved_files.append(str(ui_path)) behavior_path = standards_dir / Files.BEHAVIOR_STANDARDS behavior_path.write_text(behavior_doc, encoding='utf-8') saved_files.append(str(behavior_path)) ux_path = standards_dir / Files.UX_PATTERNS ux_path.write_text(ux_doc, encoding='utf-8') saved_files.append(str(ux_path)) component_path = standards_dir / Files.COMPONENT_INDEX component_path.write_text(component_doc, encoding='utf-8') saved_files.append(str(component_path)) # Calculate totals ui_count = (len(ui_patterns.get('buttons', {}).get('sizes', [])) + len(ui_patterns.get('modals', {}).get('sizes', [])) + len(ui_patterns.get('colors', {}).get('hex_codes', []))) behavior_count = (len(behavior_patterns.get('error_handling', {}).get('messages', [])) + len(behavior_patterns.get('loading_states', {}).get('indicators', []))) ux_count = (len(ux_patterns.get('navigation', {}).get('routing', [])) + len(ux_patterns.get('accessibility', {}).get('aria', []))) result: StandardsResultDict = { 'files': saved_files, 'patterns_count': ui_count + behavior_count + ux_count, 'success': True, 'ui_patterns_count': ui_count, 'behavior_patterns_count': behavior_count, 'ux_patterns_count': ux_count, 'components_count': len(components) } logger.info("Standards saved successfully", extra={ 'files_created': len(saved_files), 'total_patterns': result['patterns_count'], 'components': len(components) }) return result

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/srwlli/docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server