-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Epic 4: Code Generation & Adaptation
Status: Not Started
Priority: Critical
Epic Owner: AI/ML Team
Estimated Tasks: 10
Depends On: Epic 1 (Design Tokens), Epic 2 (Requirements), Epic 3 (Pattern Retrieval)
Overview
Build the code generation system that adapts retrieved shadcn/ui patterns to match extracted tokens and approved requirements. Generates production-ready TypeScript components with Tailwind CSS, ARIA attributes, Storybook stories, and provenance headers.
Goals
- Parse pattern AST to understand code structure
- Inject design tokens into component styles
- Generate Tailwind CSS classes with CSS variables
- Implement approved requirements (props, events, states)
- Add ARIA attributes and semantic HTML
- Enforce TypeScript strict mode with proper types
- Generate Storybook stories for all variants
- Add provenance headers tracking generation source
- Resolve imports and dependencies correctly
- Achieve p50 latency ≤60s for Button/Card generation
Success Criteria
- ✅ Parse TypeScript AST without errors
- ✅ Inject tokens into Tailwind classes correctly
- ✅ Generate CSS variables in component file
- ✅ Implement all approved requirements from Epic 2
- ✅ Add proper ARIA attributes (aria-label, role, etc.)
- ✅ TypeScript strict mode compilation succeeds
- ✅ No
anytypes without justification - ✅ Generate Storybook stories with all variants
- ✅ Provenance header includes pattern ID and version
- ✅ Import resolution handles shadcn/ui dependencies
- ✅ Generated code passes ESLint and Prettier
- ✅ p50 latency ≤60s for Button/Card components
- ✅ p95 latency ≤90s
Tasks
Task 1: AST Parsing & Analysis
Acceptance Criteria:
- Parse TypeScript pattern code to AST using
@babel/parser - Extract component structure:
- Component name and type (function/class)
- Props interface
- Return statement (JSX)
- Imports and dependencies
- Identify modification points:
- Style/className attributes
- Props destructuring
- Event handlers
- Conditional rendering
- Handle parsing errors gracefully
- Support both function and arrow function components
- Preserve code formatting and comments
Files:
backend/src/generation/ast_parser.py
AST Parsing:
import json
from typing import Dict, Any
import subprocess
class ASTParser:
def parse(self, code: str) -> Dict[str, Any]:
"""Parse TypeScript code to AST using Babel parser."""
# Use Node.js subprocess to parse with @babel/parser
result = subprocess.run(
['node', 'scripts/parse_ast.js'],
input=code,
capture_output=True,
text=True
)
if result.returncode != 0:
raise ValueError(f"AST parsing failed: {result.stderr}")
ast = json.loads(result.stdout)
return self._analyze_ast(ast)
def _analyze_ast(self, ast: dict) -> dict:
"""Extract component structure from AST."""
return {
"component_name": self._get_component_name(ast),
"props_interface": self._get_props_interface(ast),
"jsx_structure": self._get_jsx_structure(ast),
"imports": self._get_imports(ast),
"modification_points": self._find_modification_points(ast)
}Node.js Helper (scripts/parse_ast.js):
const parser = require('@babel/parser');
const fs = require('fs');
const code = fs.readFileSync(0, 'utf-8'); // Read from stdin
const ast = parser.parse(code, {
sourceType: 'module',
plugins: ['typescript', 'jsx']
});
console.log(JSON.stringify(ast, null, 2));Tests:
- Parse shadcn/ui Button component successfully
- Extract props interface correctly
- Identify className modification points
- Handle parsing errors gracefully
Task 2: Design Token Injection
Acceptance Criteria:
- Map extracted tokens to component styles:
- Colors → background, text, border colors
- Typography → font family, size, weight
- Spacing → padding, margin, gap
- Generate CSS variable definitions:
:root { --color-primary: #3B82F6; --font-size-base: 16px; --spacing-md: 16px; }
- Replace hardcoded values in pattern with CSS vars
- Handle token mapping for different component types:
- Button: primary color, padding, font
- Card: background, border, spacing
- Input: border color, focus ring, padding
- Preserve existing Tailwind classes when possible
- Add fallback values for missing tokens
Files:
backend/src/generation/token_injector.py
Token Injection:
class TokenInjector:
def inject(self, ast: dict, tokens: dict) -> dict:
"""Inject design tokens into component AST."""
css_vars = self._generate_css_variables(tokens)
modified_ast = self._replace_styles(ast, tokens)
return {
"ast": modified_ast,
"css_variables": css_vars,
"token_mapping": self._create_token_mapping(tokens)
}
def _generate_css_variables(self, tokens: dict) -> str:
"""Generate CSS variable definitions."""
vars = []
if "colors" in tokens:
for name, value in tokens["colors"].items():
vars.append(f" --color-{name}: {value};")
if "typography" in tokens:
if "fontSize" in tokens["typography"]:
vars.append(f" --font-size-base: {tokens['typography']['fontSize']};")
if "spacing" in tokens:
for name, value in tokens["spacing"].items():
vars.append(f" --spacing-{name}: {value};")
return ":root {\n" + "\n".join(vars) + "\n}"
def _replace_styles(self, ast: dict, tokens: dict) -> dict:
"""Replace hardcoded styles with token references."""
# Modify className attributes to use CSS variables
# Example: bg-blue-500 → bg-[var(--color-primary)]
passTests:
- CSS variables generated correctly
- Token values injected into styles
- Fallback values used when tokens missing
- Token mapping complete and accurate
Task 3: Tailwind CSS Generation
Acceptance Criteria:
- Generate Tailwind CSS classes using design tokens
- Use CSS variables for dynamic values:
bg-[var(--color-primary)]text-[var(--font-size-base)]p-[var(--spacing-md)]
- Support all Tailwind utilities:
- Colors: bg, text, border
- Spacing: p, m, gap
- Typography: font, text, leading
- Layout: flex, grid
- States: hover, focus, disabled
- Generate responsive classes when needed
- Maintain semantic class composition
- Avoid inline styles unless necessary
- Validate generated classes against Tailwind config
Files:
backend/src/generation/tailwind_generator.py
Tailwind Generation:
class TailwindGenerator:
def generate_classes(self, element: str, tokens: dict,
variant: str = None) -> str:
"""Generate Tailwind classes for element."""
classes = []
# Base classes
if element == "button":
classes.extend([
"inline-flex items-center justify-center",
"rounded-md text-sm font-medium",
"transition-colors focus-visible:outline-none",
"focus-visible:ring-2 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50"
])
# Variant-specific classes
if variant == "primary":
classes.extend([
"bg-[var(--color-primary)]",
"text-white",
"hover:bg-[var(--color-primary)]/90"
])
elif variant == "secondary":
classes.extend([
"bg-secondary text-secondary-foreground",
"hover:bg-secondary/80"
])
# Spacing from tokens
if "spacing" in tokens:
padding = tokens["spacing"].get("padding", "16px")
classes.append(f"p-[{padding}]")
return " ".join(classes)Tests:
- Tailwind classes generated correctly
- CSS variables used appropriately
- Classes validate against Tailwind config
- Responsive classes work correctly
Task 4: Requirements Implementation
Acceptance Criteria:
- Implement all approved requirements from Epic 2:
- Props: Add to interface, use in component
- Events: Add event handlers (onClick, onChange, etc.)
- States: Implement state management for hover, focus, disabled
- Validation: Add validation logic for inputs
- A11y: Add ARIA attributes
- Generate TypeScript prop types from requirements
- Add JSDoc comments for props
- Implement prop validation (required vs optional)
- Handle default values for optional props
- Generate variant logic based on props
- Add event handler type definitions
Files:
backend/src/generation/requirement_implementer.py
Requirement Implementation:
class RequirementImplementer:
def implement(self, ast: dict, requirements: dict) -> dict:
"""Implement approved requirements in component."""
modified_ast = ast.copy()
# Add props to interface
if "props" in requirements:
modified_ast = self._add_props(modified_ast, requirements["props"])
# Add event handlers
if "events" in requirements:
modified_ast = self._add_events(modified_ast, requirements["events"])
# Add state management
if "states" in requirements:
modified_ast = self._add_states(modified_ast, requirements["states"])
# Add accessibility attributes
if "accessibility" in requirements:
modified_ast = self._add_a11y(modified_ast, requirements["accessibility"])
return modified_ast
def _add_props(self, ast: dict, props: list) -> dict:
"""Add props to TypeScript interface."""
# Generate interface code
interface_code = "interface ButtonProps {\n"
for prop in props:
prop_type = self._infer_prop_type(prop)
optional = "?" if not prop.get("required") else ""
interface_code += f" {prop['name']}{optional}: {prop_type};\n"
interface_code += "}"
# Insert into AST
return astTests:
- Props added to interface correctly
- Event handlers implemented properly
- State management works as expected
- Default values handled correctly
Task 5: ARIA Attributes & Semantic HTML
Acceptance Criteria:
- Add appropriate ARIA attributes:
aria-labelfor icon-only buttonsaria-disabledfor disabled statearia-busyfor loading statearia-pressedfor toggle buttonsrolewhen semantic HTML insufficient
- Use semantic HTML elements:
<button>for clickable actions<input>for form fields<label>for input labels<fieldset>for grouped inputs
- Add keyboard navigation support:
tabIndexfor focusable elementsonKeyDownfor keyboard handlers
- Implement focus indicators
- Add screen reader text where needed
Files:
backend/src/generation/a11y_enhancer.py
A11y Enhancement:
class A11yEnhancer:
def enhance(self, ast: dict, component_type: str,
requirements: dict) -> dict:
"""Add accessibility attributes to component."""
modified_ast = ast.copy()
# Add ARIA attributes based on component type
if component_type == "button":
modified_ast = self._add_button_a11y(modified_ast, requirements)
elif component_type == "input":
modified_ast = self._add_input_a11y(modified_ast, requirements)
# Add keyboard navigation
modified_ast = self._add_keyboard_nav(modified_ast)
return modified_ast
def _add_button_a11y(self, ast: dict, requirements: dict) -> dict:
"""Add button-specific ARIA attributes."""
aria_attrs = []
# Check for icon-only variant
if "icon-only" in requirements.get("variants", []):
aria_attrs.append('aria-label="{label}"')
# Add disabled state
aria_attrs.append('aria-disabled={disabled}')
# Add loading state if required
if "loading" in requirements.get("states", []):
aria_attrs.append('aria-busy={loading}')
return astTests:
- ARIA attributes added correctly
- Semantic HTML used appropriately
- Keyboard navigation works
- Focus indicators visible
Task 6: TypeScript Type Safety
Acceptance Criteria:
- Generate strict TypeScript with no
anytypes - Define prop interfaces with proper types
- Add return type annotations for functions
- Use TypeScript utility types where appropriate:
Omit,Pick,Partial, etc.
- Add JSDoc comments for complex types
- Generate union types for variants
- Handle ref forwarding with proper types
- Validate generated code with
tsc --noEmit - Fix common TypeScript errors automatically
Files:
backend/src/generation/type_generator.py
Type Generation:
class TypeGenerator:
def generate_types(self, requirements: dict) -> str:
"""Generate TypeScript type definitions."""
types = []
# Variant union type
if "variant" in requirements.get("props", []):
variants = requirements["props"]["variant"]["values"]
variant_type = " | ".join(f'"{v}"' for v in variants)
types.append(f'type Variant = {variant_type};')
# Props interface
props_interface = self._generate_props_interface(requirements)
types.append(props_interface)
return "\n\n".join(types)
def _generate_props_interface(self, requirements: dict) -> str:
"""Generate props interface."""
code = "interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n"
for prop in requirements.get("props", []):
prop_name = prop["name"]
prop_type = self._get_prop_type(prop)
optional = "?" if not prop.get("required") else ""
# Add JSDoc if available
if "description" in prop:
code += f' /** {prop["description"]} */\n'
code += f" {prop_name}{optional}: {prop_type};\n"
code += "}"
return code
def _get_prop_type(self, prop: dict) -> str:
"""Infer TypeScript type from prop definition."""
if prop.get("values"):
# Union type for enums
return " | ".join(f'"{v}"' for v in prop["values"])
elif prop.get("type") == "boolean":
return "boolean"
elif prop.get("type") == "function":
return "() => void"
else:
return "string"Tests:
- TypeScript compilation succeeds
- No
anytypes in generated code - Union types correct for variants
- Ref forwarding typed correctly
Task 7: Storybook Story Generation
Acceptance Criteria:
- Generate Storybook stories for component
- Create stories for all variants
- Include interactive controls for props
- Add documentation in MDX format
- Generate example code snippets
- Include accessibility testing addon config
- Add story for each state (default, hover, disabled, loading)
- Use Storybook 8 format (CSF 3.0)
- Generate play functions for interaction testing
Files:
backend/src/generation/storybook_generator.py
Storybook Generation:
class StorybookGenerator:
def generate_stories(self, component_name: str,
requirements: dict, tokens: dict) -> str:
"""Generate Storybook stories file."""
stories = f"""import type {{ Meta, StoryObj }} from '@storybook/react';
import {{ {component_name} }} from './{component_name}';
const meta: Meta<typeof {component_name}> = {{
title: 'Components/{component_name}',
component: {component_name},
tags: ['autodocs'],
argTypes: {{
{self._generate_arg_types(requirements)}
}},
}};
export default meta;
type Story = StoryObj<typeof {component_name}>;
{self._generate_variant_stories(component_name, requirements)}
"""
return stories
def _generate_arg_types(self, requirements: dict) -> str:
"""Generate argTypes for controls."""
arg_types = []
for prop in requirements.get("props", []):
if prop.get("values"):
# Enum control
arg_types.append(f""" {prop['name']}: {{
control: 'select',
options: {prop['values']},
}}""")
elif prop.get("type") == "boolean":
arg_types.append(f""" {prop['name']}: {{
control: 'boolean',
}}""")
return ",\n".join(arg_types)
def _generate_variant_stories(self, component_name: str,
requirements: dict) -> str:
"""Generate story for each variant."""
stories = []
# Default story
stories.append(f"""export const Default: Story = {{
args: {{
children: 'Button',
}},
}};""")
# Variant stories
variants = next((p["values"] for p in requirements.get("props", [])
if p["name"] == "variant"), [])
for variant in variants:
story_name = variant.capitalize()
stories.append(f"""
export const {story_name}: Story = {{
args: {{
variant: '{variant}',
children: '{story_name} Button',
}},
}};""")
return "\n".join(stories)Tests:
- Storybook stories render correctly
- All variants have stories
- Controls work for interactive props
- Documentation renders properly
Task 8: Provenance Headers & Metadata
Acceptance Criteria:
- Add provenance header comment to generated files:
/** * Generated by ComponentForge * Pattern: shadcn-ui-button-v1.2.0 * Pattern ID: pattern-button-001 * Generated: 2025-10-03T10:00:00Z * Tokens Hash: abc123def456 * Requirements Hash: def789ghi012 * DO NOT EDIT: Regenerate instead */
- Include generation metadata in header
- Add warning about manual edits
- Track pattern version for regeneration
- Store provenance data in PostgreSQL
- Enable version comparison for updates
Files:
backend/src/generation/provenance.py
Provenance Header:
import hashlib
from datetime import datetime
class ProvenanceGenerator:
def generate_header(self, pattern: dict, tokens: dict,
requirements: dict) -> str:
"""Generate provenance header comment."""
tokens_hash = self._hash_dict(tokens)
requirements_hash = self._hash_dict(requirements)
timestamp = datetime.utcnow().isoformat() + "Z"
return f"""/**
* Generated by ComponentForge
* Pattern: {pattern['name']}-{pattern['version']}
* Pattern ID: {pattern['id']}
* Generated: {timestamp}
* Tokens Hash: {tokens_hash}
* Requirements Hash: {requirements_hash}
* DO NOT EDIT: Regenerate with `componentforge regenerate`
*/
"""
def _hash_dict(self, data: dict) -> str:
"""Generate hash of dictionary for change detection."""
json_str = json.dumps(data, sort_keys=True)
return hashlib.sha256(json_str.encode()).hexdigest()[:16]Tests:
- Provenance header added to all files
- Hashes calculated correctly
- Metadata stored in database
- Version tracking works
Task 9: Import Resolution & Dependencies
Acceptance Criteria:
- Resolve all import statements correctly:
- React imports
- shadcn/ui component imports
- Utility imports (cn, clsx)
- Icon imports (lucide-react)
- Generate import statements in correct order:
- External libraries
- Internal components
- Utilities
- Types
- Handle alias imports (@/ for src/)
- Add missing imports automatically
- Remove unused imports
- Validate import paths exist
- Generate package.json dependencies list
Files:
backend/src/generation/import_resolver.py
Import Resolution:
class ImportResolver:
def resolve(self, ast: dict, component_type: str) -> list[str]:
"""Resolve and generate import statements."""
imports = []
# React imports
react_imports = ["React"]
if "useState" in ast or "useEffect" in ast:
react_imports.append("{ useState }")
imports.append(f"import {', '.join(react_imports)} from 'react';")
# shadcn/ui imports
if component_type == "button":
imports.append("import { cva, type VariantProps } from 'class-variance-authority';")
# Utility imports
imports.append("import { cn } from '@/lib/utils';")
# Icon imports if needed
if self._has_icons(ast):
imports.append("import { Loader2 } from 'lucide-react';")
return imports
def _has_icons(self, ast: dict) -> bool:
"""Check if component uses icons."""
# Analyze AST for icon components
return FalseTests:
- All imports resolved correctly
- Import order matches convention
- Unused imports removed
- Missing imports added
Task 10: Code Assembly & Validation
Acceptance Criteria:
- Assemble final component code from:
- Imports
- Provenance header
- Type definitions
- CSS variables
- Component implementation
- Format code with Prettier
- Validate TypeScript compilation with
tsc --noEmit - Run ESLint and fix auto-fixable issues
- Generate component file and Storybook stories
- Measure generation latency (p50, p95, p99)
- Target: p50 ≤60s for Button/Card
- Store generated files to S3
- Store metadata to PostgreSQL
- Return generation result with timing and cost
Files:
backend/src/generation/code_assembler.pybackend/src/generation/generator_service.py
Code Assembly:
class CodeAssembler:
async def assemble(self, parts: dict) -> dict:
"""Assemble final component code."""
# Build component file
component_code = "\n\n".join([
parts["provenance_header"],
"\n".join(parts["imports"]),
parts["css_variables"],
parts["type_definitions"],
parts["component_code"]
])
# Format with Prettier
formatted = await self._format_code(component_code)
# Build stories file
stories_code = parts["storybook_stories"]
formatted_stories = await self._format_code(stories_code)
return {
"component": formatted,
"stories": formatted_stories,
"files": {
f"{parts['component_name']}.tsx": formatted,
f"{parts['component_name']}.stories.tsx": formatted_stories
}
}
async def _format_code(self, code: str) -> str:
"""Format code with Prettier."""
result = await asyncio.create_subprocess_exec(
'npx', 'prettier', '--parser', 'typescript',
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await result.communicate(code.encode())
if result.returncode != 0:
raise ValueError(f"Prettier failed: {stderr.decode()}")
return stdout.decode()Tests:
- Component assembles correctly
- Prettier formatting succeeds
- TypeScript compilation passes
- ESLint validation passes
- Latency within targets
Dependencies
Requires:
- Epic 1: Design tokens for injection
- Epic 2: Requirements for implementation
- Epic 3: Retrieved patterns for adaptation
Blocks:
- Epic 5: Quality validation needs generated code
Technical Architecture
Code Generation Flow
Retrieved Pattern + Tokens + Requirements
↓
AST Parsing
↓
Token Injection
↓
Tailwind Generation
↓
Requirements Implementation
↓
A11y Enhancement
↓
Type Generation
↓
Storybook Generation
↓
Provenance Header
↓
Import Resolution
↓
Code Assembly
↓
Format + Validate
↓
Store to S3 + PostgreSQL
Metrics
| Metric | Target | Measurement |
|---|---|---|
| Latency (p50) | ≤60s | LangSmith traces for Button/Card |
| Latency (p95) | ≤90s | 95th percentile generation time |
| TypeScript Compilation | 100% | All generated components compile |
| Token Injection Accuracy | ≥95% | Correct token values in code |
| Requirements Implementation | 100% | All approved requirements present |
Risks & Mitigation
| Risk | Impact | Mitigation |
|---|---|---|
| Generation too slow (>60s) | High | Optimize AST operations, cache patterns |
| TypeScript compilation errors | High | Better AST manipulation, validation step |
| Incorrect token injection | Medium | Comprehensive tests, manual review |
| Import resolution failures | Medium | Fallback to common imports, validation |
| Storybook stories broken | Low | Test story rendering, simpler stories |
Definition of Done
- All 10 tasks completed with acceptance criteria met
- Generate Button and Card components successfully
- TypeScript strict compilation passes
- Tokens injected correctly (≥95% accuracy)
- All approved requirements implemented
- ARIA attributes added appropriately
- Storybook stories render correctly
- Provenance headers present
- Imports resolved correctly
- p50 latency ≤60s for Button/Card
- Integration tests with Epic 5 passing
- Documentation updated
Related Epics
- Depends On: Epic 1, Epic 2, Epic 3
- Blocks: Epic 5
- Related: Epic 8 (regeneration uses same pipeline)
Notes
Critical Path: This is the core value delivery epic. Quality here determines product success.
Performance: 60s target is aggressive. Monitor carefully and optimize AST operations first.
Type Safety: Zero tolerance for any types. This is a quality differentiator.