Agent skill
scaffolder
Generates boilerplate code following loaded rules. Creates new components, modules, APIs, and features that automatically comply with your coding standards. Extracts patterns from rules and applies them consistently.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/scaffolder
SKILL.md
Step 1: Load Rule Index
Load the rule index to discover relevant rules dynamically:
- @.claude/context/rule-index.json
Step 2: Identify Target Framework and Query Index
Determine which technologies apply based on what you're scaffolding:
Component scaffolding:
- Detect: React, Next.js, TypeScript
- Query:
index.technology_map['react'],index.technology_map['nextjs'],index.technology_map['typescript']
API Route scaffolding:
- Detect: Next.js App Router or FastAPI
- Query:
index.technology_map['nextjs']orindex.technology_map['fastapi']
Test File scaffolding:
- Detect: Jest, Cypress, Playwright, Vitest, pytest
- Query:
index.technology_map['jest'],index.technology_map['cypress'], etc.
Database Model scaffolding:
- Detect: Prisma, SQL, database patterns
- Query:
index.technology_map['prisma']or database-related rules
Step 3: Load Relevant Rules
Load only the relevant rule files from the index (progressive disclosure):
- Master rules first (from
.claude/rules-master/) - Archive rules supplement (from
.claude/archive/) - Load 3-5 most relevant rules, not all 1,081
Step 4: Extract Patterns from Rules
Parse the loaded rule files to extract scaffolding patterns:
From Next.js rules (TECH_STACK_NEXTJS.md or nextjs.mdc):
- Server Components by default
- 'use client' only when needed
- Place in
app/for routes,components/for shared - Use lowercase-with-dashes for directories
From TypeScript rules:
- Interfaces for object shapes
- Proper return type annotations
- Avoid
any, useunknown - PascalCase for types/interfaces
From React rules:
- Functional components only
- Custom hooks for reusable logic
- Props interface for each component
- Error boundaries for critical sections
Step 5: Generate Compliant Code
Apply extracted patterns to generate code that passes rule-auditor. </execution_process>
<best_practices>
- Always Audit After: Run
/auditafter scaffolding to catch any edge cases - Customize Templates: Add project-specific patterns to rules for consistent generation
- Use for Consistency: Scaffold even simple files to maintain team conventions
- Review Generated Code: Scaffolded code is a starting point, not final implementation
- Keep Rules Updated: As patterns evolve, update rules so scaffolder stays current </best_practices>
1. User: /scaffold component UserDashboard
2. Scaffolder reads: nextjs.mdc, typescript.mdc, react.mdc
3. Extracts patterns: Server Component, Suspense, interfaces
4. Generates compliant code structure
5. Writes files to correct locations
6. Runs rule-auditor to verify compliance
7. Reports any manual adjustments needed
Feature Module Generation
For larger features, scaffold generates a complete module:
/scaffold feature user-management
Generates:
app/
└── (dashboard)/
└── users/
├── page.tsx # List page
├── [id]/
│ └── page.tsx # Detail page
├── new/
│ └── page.tsx # Create page
└── components/
├── user-list.tsx
├── user-card.tsx
└── user-form.tsx
components/
└── users/
└── ... (shared components)
lib/
└── users/
├── api.ts # API functions
├── types.ts # Type definitions
└── validations.ts # Zod schemas
Command: /scaffold component UserProfile
Generated: components/user-profile/index.tsx
// Server Component (default per nextjs.mdc)
// Location: components/user-profile/ (lowercase-with-dashes per nextjs.mdc)
import { Suspense } from 'react'
import { UserProfileSkeleton } from './skeleton'
import { UserProfileContent } from './content'
// Interface defined (per typescript.mdc)
interface UserProfileProps {
userId: string
showDetails?: boolean
}
// Async Server Component for data fetching (per nextjs.mdc > Data Fetching)
export async function UserProfile({ userId, showDetails = false }: UserProfileProps) {
return (
// Suspense boundary (per nextjs.mdc > Components)
<Suspense fallback={<UserProfileSkeleton />}>
<UserProfileContent userId={userId} showDetails={showDetails} />
</Suspense>
)
}
// Default export for dynamic imports (per nextjs.mdc)
export default UserProfile
Also generates:
components/user-profile/content.tsx- Async content componentcomponents/user-profile/skeleton.tsx- Loading skeletoncomponents/user-profile/types.ts- Shared typescomponents/user-profile/index.ts- Barrel export </code_example>
<code_example> Next.js Client Component
Command: /scaffold client-component SearchBar
Generated: components/search-bar/index.tsx
'use client' // Required for useState (per nextjs.mdc > Components)
import { useState, useCallback } from 'react'
import { useDebounce } from '@/hooks/use-debounce'
// Props interface (per typescript.mdc)
interface SearchBarProps {
onSearch: (query: string) => void
placeholder?: string
debounceMs?: number
}
// Functional component (per react.mdc)
export function SearchBar({
onSearch,
placeholder = 'Search...',
debounceMs = 300,
}: SearchBarProps) {
// Minimal client state (per nextjs.mdc > State Management)
const [query, setQuery] = useState('')
// Debounced callback (per nextjs.mdc > Performance)
const debouncedSearch = useDebounce(onSearch, debounceMs)
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setQuery(value)
debouncedSearch(value)
}, [debouncedSearch])
return (
<input
type="search"
value={query}
onChange={handleChange}
placeholder={placeholder}
className="w-full px-4 py-2 border rounded-lg" // Tailwind (per tailwind.mdc)
aria-label={placeholder} // Accessibility
/>
)
}
export default SearchBar
</code_example>
<code_example> Next.js API Route (App Router)
Command: /scaffold api users
Generated: app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' // Zod for validation (per nextjs.mdc > Forms and Validation)
// Request schema (per typescript.mdc > Type System)
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
})
// Response type (per typescript.mdc)
interface UserResponse {
id: string
email: string
name: string
createdAt: string
}
// GET handler (per nextjs.mdc > Routing)
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') ?? '1')
const limit = parseInt(searchParams.get('limit') ?? '10')
// TODO: Replace with actual database query
const users: UserResponse[] = []
return NextResponse.json({
data: users,
pagination: { page, limit, total: 0 },
})
} catch (error) {
// Proper error handling (per nextjs.mdc > Data Fetching)
console.error('Failed to fetch users:', error)
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
)
}
}
// POST handler with validation
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Server-side validation (per nextjs.mdc > Forms and Validation)
const validated = CreateUserSchema.parse(body)
// TODO: Replace with actual database insert
const user: UserResponse = {
id: crypto.randomUUID(),
...validated,
createdAt: new Date().toISOString(),
}
return NextResponse.json({ data: user }, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
)
}
console.error('Failed to create user:', error)
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
)
}
}
</code_example>
<code_example> FastAPI Endpoint
Command: /scaffold fastapi-route users
Generated: app/routers/users.py
"""User management endpoints."""
# Type hints required (per python.mdc)
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, EmailStr, Field
# Router with tags (per fastapi.mdc > API Design)
router = APIRouter(prefix="/users", tags=["users"])
# Pydantic models (per fastapi.mdc > Components and Validation)
class UserCreate(BaseModel):
"""Schema for creating a user."""
email: EmailStr
name: str = Field(..., min_length=2, max_length=100)
class UserResponse(BaseModel):
"""Schema for user response."""
id: UUID
email: EmailStr
name: str
created_at: str
class Config:
from_attributes = True
class PaginatedResponse(BaseModel):
"""Paginated response wrapper."""
data: list[UserResponse]
total: int
page: int
limit: int
# Dependency injection (per fastapi.mdc > Dependency Injection)
async def get_db():
"""Database session dependency."""
# TODO: Replace with actual database session
yield None
# GET endpoint with pagination (per fastapi.mdc > Performance)
@router.get("", response_model=PaginatedResponse)
async def list_users(
db: Annotated[None, Depends(get_db)],
page: Annotated[int, Query(ge=1)] = 1,
limit: Annotated[int, Query(ge=1, le=100)] = 10,
) -> PaginatedResponse:
"""
List all users with pagination.
- **page**: Page number (starting from 1)
- **limit**: Items per page (max 100)
"""
# TODO: Replace with actual database query
users: list[UserResponse] = []
total = 0
return PaginatedResponse(data=users, total=total, page=page, limit=limit)
# POST endpoint with validation
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(
user_data: UserCreate,
db: Annotated[None, Depends(get_db)],
) -> UserResponse:
"""
Create a new user.
- **email**: Valid email address
- **name**: User's display name (2-100 chars)
"""
# TODO: Replace with actual database insert
# Check for existing user, create, return
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Database integration pending",
)
# GET by ID endpoint
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: UUID,
db: Annotated[None, Depends(get_db)],
) -> UserResponse:
"""Get a specific user by ID."""
# TODO: Replace with actual database query
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User {user_id} not found",
)
</code_example>
<code_example> Test File (Vitest/Jest)
Command: /scaffold test components/user-profile
Generated: components/user-profile/__tests__/index.test.tsx
// Test file (per jest-*.mdc / vitest-*.mdc patterns)
import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { UserProfile } from '../index'
// Mock external dependencies (per testing best practices)
vi.mock('@/lib/api', () => ({
fetchUser: vi.fn(),
}))
describe('UserProfile', () => {
// Clear mocks before each test (per clean-code.mdc)
beforeEach(() => {
vi.clearAllMocks()
})
// Descriptive test names (per testing guidelines)
it('renders loading skeleton initially', () => {
render(<UserProfile userId="123" />)
expect(screen.getByTestId('user-profile-skeleton')).toBeInTheDocument()
})
it('displays user information after loading', async () => {
const mockUser = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
}
const { fetchUser } = await import('@/lib/api')
vi.mocked(fetchUser).mockResolvedValue(mockUser)
render(<UserProfile userId="123" />)
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('john@example.com')).toBeInTheDocument()
})
})
it('handles error state gracefully', async () => {
const { fetchUser } = await import('@/lib/api')
vi.mocked(fetchUser).mockRejectedValue(new Error('Network error'))
render(<UserProfile userId="123" />)
await waitFor(() => {
expect(screen.getByText(/failed to load/i)).toBeInTheDocument()
})
})
// Edge cases (per qa best practices)
it('shows details when showDetails prop is true', async () => {
render(<UserProfile userId="123" showDetails />)
await waitFor(() => {
expect(screen.getByTestId('user-details-section')).toBeInTheDocument()
})
})
})
</code_example>
<code_example> Cypress E2E Test
Command: /scaffold e2e-test user-flow
Generated: cypress/e2e/user-flow.cy.ts
// E2E test (per cypress-e2e-testing-*.mdc)
describe('User Flow', () => {
// Setup before tests (per cypress best practices)
beforeEach(() => {
// Reset state and seed data
cy.task('db:seed')
cy.visit('/')
})
// Critical user flow (per cypress-e2e-testing guidelines)
it('allows user to sign up, login, and view profile', () => {
// Use data-testid selectors (per cypress best practices)
cy.get('[data-testid="signup-link"]').click()
// Fill signup form
cy.get('[data-testid="email-input"]').type('test@example.com')
cy.get('[data-testid="password-input"]').type('SecurePass123!')
cy.get('[data-testid="name-input"]').type('Test User')
cy.get('[data-testid="signup-submit"]').click()
// Verify redirect to dashboard
cy.url().should('include', '/dashboard')
// Navigate to profile
cy.get('[data-testid="profile-link"]').click()
// Verify profile data
cy.get('[data-testid="profile-name"]').should('contain', 'Test User')
cy.get('[data-testid="profile-email"]').should('contain', 'test@example.com')
})
// API mocking example (per cypress-api-testing guidelines)
it('handles API errors gracefully', () => {
// Mock API failure
cy.intercept('GET', '/api/users/*', {
statusCode: 500,
body: { error: 'Internal server error' },
}).as('getUserError')
cy.visit('/profile')
// Wait for mocked request
cy.wait('@getUserError')
// Verify error handling
cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Failed to load profile')
// Verify retry option
cy.get('[data-testid="retry-button"]').should('be.visible')
})
})
</code_example>
<usage_example> Quick Commands:
# Generate a Server Component
/scaffold component MyComponent
# Generate a Client Component
/scaffold client-component MyInteractiveWidget
# Generate an API route
/scaffold api resource-name
# Generate a FastAPI router
/scaffold fastapi-route resource-name
# Generate test for existing file
/scaffold test path/to/component
# Generate E2E test
/scaffold e2e-test flow-name
# Generate with specific rules
/scaffold component MyComponent --rules nextjs,typescript
# Generate in specific location
/scaffold component MyComponent --path src/features/auth
# List available scaffold templates
/scaffold --list
Available Templates:
| Template | Framework | Files Generated |
|---|---|---|
component |
Next.js/React | index.tsx, types.ts, skeleton.tsx |
client-component |
Next.js | index.tsx with 'use client' |
page |
Next.js App Router | page.tsx, loading.tsx, error.tsx |
api |
Next.js App Router | route.ts with handlers |
fastapi-route |
FastAPI | router file with endpoints |
hook |
React | Custom hook with types |
context |
React | Context provider + hook |
test |
Jest/Vitest | Test file for component |
e2e-test |
Cypress/Playwright | E2E test spec |
model |
Prisma | Schema model |
migration |
Database | Migration file |
| </usage_example> | ||
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?