Best Practices
by Vuong Ngo

Scaling AI-Assisted Development: The Intelligent Scaffolding Approach

From MVP to Mature: Where AI Assistance Breaks Down

When you're building an MVP, AI coding assistants feel like magic. "Build me a login page"—boom, it's done. "Add a database connection"—perfect. But as your project evolves from 100 lines to 100,000 lines, something changes:

Phase 1: The Honeymoon (0-10K lines)

  • AI writes everything from scratch ✓
  • No conventions needed ✓
  • Pure productivity ✓

Phase 2: Pattern Emergence (10K-50K lines)

  • You notice inconsistencies ⚠️
  • "Why does this component use hooks differently?"
  • "Didn't we already build this pattern?"

Phase 3: The Documentation Spiral (50K+ lines)

  • Custom instructions get longer and longer
  • .md files multiply like rabbits
  • Context window maxes out ✗
  • AI "forgets" your conventions ✗

Sound familiar? This is where traditional AI assistance hits a wall.

My Personal Journey (The Failed Experiments)

I maintain a large monorepo with frontend apps (Next.js, TanStack Start), backend APIs (Hono.js, FastAPI, Lambda, etc...), shared packages, and infrastructure—all in one place. Over the years, I've built reusable design systems, theming, deployment patterns, and coding standards. Getting AI to respect these patterns? That was the challenge.

Attempt 1: The CLAUDE.md Approach

Like many developers, I started with comprehensive CLAUDE.md files referencing documentation via @:

  • Project Structure
  • Coding Standards
  • Technology Stack
  • Conventions
  • Style System
  • Development Process

Result? Even with token-efficient documentation, I couldn't cover all design patterns and coding standards in a multi-language monorepo. AI still made mistakes.

Attempt 2: Per-Directory CLAUDE.md Files

"Maybe collocated instructions work better?" I created 50+ CLAUDE.md files for different apps, APIs, and packages.

Result? Slightly better when the collocated CLAUDE.md loaded in context (which didn't always happen). But the real issue: I only had ~10 distinct patterns across the entire monorepo. Maintaining 50+ instruction files for 10 patterns? Nightmare.

Attempt 3: Autonomous Workflows

I set up an autonomous workflow (PRD → code → lint + test → fix loop) to build internal libraries.

Result? Oh man. I spent way more time removing code and fixing bugs than if I'd just coded it myself. No matter how many times I updated CLAUDE.md, the issues persisted.

The Three Pain Points

1. Inconsistency Across Codebase

// File 1: AI generated last week
export function UserProfile({ user }: Props) {
  return <div className="user-profile">...</div>
}

// File 2: AI generated yesterday  
export const ProfileCard = (props: ProfileProps) => {
  return <div className={styles.profile}>...</div>
}

// File 3: AI generated today
function UserCard({ userData }: UserCardProps) {
  return <div className="profile-card">...</div>
}

Three different patterns for the same concept. All technically correct. All maintenance nightmares.

2. Context Window Overload

Your .md documentation grows from this:

# Conventions
- Use functional components
- Use TypeScript

To this monstrosity:

# Conventions (120 pages)
## Components
- Use functional components
- Props interface must be exported
- Use PascalCase for component names
- Export from index.ts barrel files
- Tests in same directory with .test.tsx
- Storybook stories with .stories.tsx
...
(118 more pages)

Eventually, even AI can't keep up.

3. Pattern Recreation Waste

How many times have you watched AI recreate the same pattern?

  • Authenticated API routes with similar structure
  • Form components with validation logic
  • Database models with timestamp fields
  • Service classes that all need the same base configuration

Each time slightly different. Each time you review, fix, standardize. Hours wasted on work already done.

What is Intelligent Scaffolding?

Instead of fighting these problems with longer instructions, we need a fundamental shift: teach AI to use templates, not just write code.

This is where the AI Code Toolkit and @agiflowai/scaffold-mcp come in.

How It Works: The scaffolding approach leverages MCP (Model Context Protocol) to expose template generation as a tool that AI agents can call. It uses structured output (JSON Schema validation) for the initial code generation, ensuring variables are properly typed and validated. This generated code then serves as guided generation for the LLM—providing a solid foundation that follows your patterns, which the AI can then enhance with context-specific logic. Think of it as "fill-in-the-blanks" coding: the structure is guaranteed consistent, while the AI adds intelligence where it matters.

The Three Core Pillars

As projects evolve from MVP to mature state, they develop patterns, conventions, and opinionated approaches. Unfortunately, custom instructions (prompts) alone don't always ensure coding agents follow your requirements. This toolkit provides three essential building blocks:

Pillar 1: Scaffolding Templates

Scaffolding ensures standardization. Combining templating with LLMs generates code that follows your internal conventions while reducing template maintenance effort. Think of it as giving AI a proven blueprint instead of asking it to improvise every time.

Pillar 2: Architecture + Design Patterns

As projects grow, convention becomes more important than configuration. Frameworks like Ruby on Rails and Angular demonstrate how opinionated approaches make code easier to find and understand—the same principle applies to coding agents. These are the pre-steps that guide the AI before writing code.

Pillar 3: Rules

Think of architecture and design patterns as pre-steps to guide the coding agent, and rules as post-checks to enforce your processes. Rules can be quantitative or qualitative, providing programmatic validation of agent outputs.

Why MCP (Model Context Protocol)?

The @agiflowai/scaffold-mcp package is an MCP server, which means:

  • Agent-agnostic: Works with Claude Code, Cursor, or any MCP-compatible tool
  • Tech stack agnostic: Works with any framework - Next.js, Vite React, or your custom setup
  • Multiple modes: MCP server mode (stdio/HTTP/SSE) and standalone CLI mode
  • Always available: AI can use it just like any other tool, or you can script deterministic workflows with CLI commands

How to Use Scaffolding in Your Daily Workflow

Let's get practical. Here's how scaffolding transforms your daily development.

Scenario 1: Starting a New Project

Without scaffolding:

You: "Create a new Next.js 15 project with TypeScript"
AI: *generates 50 files*
You: "Wait, why is the config different from our other projects?"
You: "Can you add our standard ESLint config?"
You: "Actually, use our Tailwind setup..."
*30 minutes of back-and-forth*

With scaffolding:

# See what templates are available
scaffold-mcp boilerplate list

# Output:
# - nextjs-15-boilerplate: Next.js 15 with App Router, TypeScript, Tailwind
# - react-vite-boilerplate: React + Vite + TypeScript
# - nodejs-api-boilerplate: Node.js API with Express

# Create project with your exact conventions
scaffold-mcp boilerplate create nextjs-15-boilerplate \
  --vars '{"projectName":"user-dashboard","packageName":"@myorg/user-dashboard"}'

# ✓ Complete project structure created
# ✓ All following your team's conventions
# ✓ Minutes instead of hours

Using Claude Code with MCP: Simply say: "Create a new Next.js project called user-dashboard"

Claude sees the scaffold-mcp MCP server, calls the use-boilerplate tool, and creates your project—perfectly consistent with your other projects.

Scenario 2: Adding Features to Existing Projects

Without scaffolding:

You: "Add a new dashboard page"
AI: *creates page with different structure than existing pages*
You: "No, use the same layout as our other pages"
AI: *creates new layout that doesn't match*
You: "Just copy the pattern from /app/users/page.tsx"
*More back-and-forth*

With scaffolding:

# What features can I add?
scaffold-mcp scaffold list ./apps/my-app

# Output:
# - scaffold-nextjs-page: Add a new App Router page
# - scaffold-react-component: Add a React component with tests
# - scaffold-api-route: Add an API endpoint

# Add a page that matches your exact pattern
scaffold-mcp scaffold add scaffold-nextjs-page \
  --project ./apps/my-app \
  --vars '{"pageTitle":"User Dashboard","nextjsPagePath":"/dashboard"}'

# ✓ src/app/dashboard/page.tsx created
# ✓ Matches existing page structure
# ✓ Includes your standard metadata
# ✓ Ready to use

Using Claude Code: "Add a dashboard page to my app"

Claude uses the scaffold-mcp to ensure the new page matches your existing pages perfectly.

Scenario 3: Team Consistency

The Problem:

  • Developer A creates components one way
  • Developer B creates them differently
  • AI learns from both and creates a third way
  • Code reviews become style debates

The Solution: Everyone (including AI) uses the same templates:

# New team member day 1:
scaffold-mcp scaffold add scaffold-react-component \
  --project ./src \
  --vars '{"componentName":"UserCard","withTests":true}'

# Output matches team conventions perfectly
# No debate, no review comments about style
# Just works

Creating Your Own Templates

Now here's where it gets powerful: you can create custom templates that encode your team's exact conventions.

Step 1: Installation and Setup

# Install the package
npm install -g @agiflowai/scaffold-mcp
# or
pnpm install @agiflowai/scaffold-mcp

# Initialize your templates folder
scaffold-mcp init

# Or specify a custom path
scaffold-mcp init --path ./my-templates

# This creates:
# templates/
#   └── (ready for your templates)

Step 2: Enable Admin Tools

To create and manage templates, enable admin mode in your Claude Code config:

{
  "mcpServers": {
    "scaffold-mcp": {
      "command": "npx",
      "args": ["-y", "@agiflowai/scaffold-mcp", "mcp-serve", "--admin-enable"]
    }
  }
}

Now restart Claude Code. You now have access to template creation tools.

Step 3: Create Your First Template

Let's create a template for your team's standard React component pattern.

Tell Claude:

"I want to create a new boilerplate template called 'react-component-library' 
in a template folder called 'my-react-template'. It should create React 
components in the packages/ folder with this structure:
- Component in PascalCase
- Storybook story file
- Test file
- Index barrel export"

Claude will use the admin tool generate-boilerplate:

This creates a scaffold.yaml configuration in your template folder with the proper structure:

# templates/my-react-template/scaffold.yaml
boilerplate:
  name: react-component-library
  description: React component library with Storybook and tests
  targetFolder: packages

  variables_schema:
    type: object
    properties:
      componentName:
        type: string
        pattern: "^[A-Z][a-zA-Z0-9]*$"
        description: Component name in PascalCase
        example: Button
      withStorybook:
        type: boolean
        description: Include Storybook story
        default: true
    required:
      - componentName
    additionalProperties: false

  includes:
    - src/{{ componentName }}/{{ componentName }}.tsx
    - src/{{ componentName }}/{{ componentName }}.stories.tsx?withStorybook=true
    - src/{{ componentName }}/{{ componentName }}.test.tsx
    - src/{{ componentName }}/index.ts

  instruction: |
    Component created in packages/ following team conventions

Step 4: Create Template Files

Now create the actual template content:

Tell Claude:

"Create the component template file that uses Tailwind CSS and exports 
a typed Props interface"

Claude uses generate-boilerplate-file:

This creates actual template files with the .liquid extension. The .liquid extension tells scaffold-mcp to process variable replacement, and it's automatically stripped in the output.

File: templates/my-react-template/src/{{ componentName }}/{{ componentName }}.tsx.liquid

{% comment %}
This component follows our team's component pattern with Tailwind CSS
{% endcomment %}
import React from 'react';

export interface {{ componentName }}Props {
  /**
   * The content of the component
   */
  children?: React.ReactNode;
  /**
   * Additional CSS classes
   */
  className?: string;
}

/**
 * {{ componentName }} component
 *
 * @example
 * <{{ componentName }}>Hello World</{{ componentName }}>
 */
export const {{ componentName }}: React.FC<{{ componentName }}Props> = ({
  children,
  className = ''
}) => {
  return (
    <div className={`{{ componentName | kebabCase }} ${className}`}>
      {children}
    </div>
  );
};

Understanding Liquid Template Syntax:

  • {{ componentName }} - Variable interpolation (gets replaced with actual value)
  • {{ componentName | kebabCase }} - Variable with filter transformation (MyButton → my-button)
  • .liquid extension - Tells scaffold-mcp to process this file and strip the extension
  • {% comment %}...{% endcomment %} - Comments (won't appear in output)
  • ?withStorybook=true in includes - Conditional include (only if withStorybook is true)

Step 5: Create Additional Template Files

Storybook story:

// File: templates/my-react-template/src/{{ componentName }}/{{ componentName }}.stories.tsx.liquid
import type { Meta, StoryObj } from '@storybook/react';
import { {{ componentName }} } from './{{ componentName }}';

const meta: Meta<typeof {{ componentName }}> = {
  title: 'Components/{{ componentName }}',
  component: {{ componentName }},
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof {{ componentName }}>;

export const Default: Story = {
  args: {
    children: '{{ componentName }} content',
  },
};

Test file:

// File: templates/my-react-template/src/{{ componentName }}/{{ componentName }}.test.tsx.liquid
import { render, screen } from '@testing-library/react';
import { {{ componentName }} } from './{{ componentName }}';

describe('{{ componentName }}', () => {
  it('renders children correctly', () => {
    render(<{{ componentName }}>Test content</{{ componentName }}>);
    expect(screen.getByText('Test content')).toBeInTheDocument();
  });

  it('applies custom className', () => {
    const { container } = render(
      <{{ componentName }} className="custom-class">Content</{{ componentName }}>
    );
    expect(container.firstChild).toHaveClass('custom-class');
  });
});

Index barrel export:

// File: templates/my-react-template/src/{{ componentName }}/index.ts.liquid
export { {{ componentName }} } from './{{ componentName }}';
export type { {{ componentName }}Props } from './{{ componentName }}';

Step 6: Use Your New Template

# Now anyone can use your template:
scaffold-mcp boilerplate create react-component-library \
  --vars '{"componentName":"Button","withStorybook":true}'

# Output:
# ✓ Created packages/Button/Button.tsx
# ✓ Created packages/Button/Button.stories.tsx  
# ✓ Created packages/Button/Button.test.tsx
# ✓ Created packages/Button/index.ts
# 
# All files follow your exact conventions!

Or with Claude Code:

"Create a new Button component using our component library template"

Step 7: Add Feature Scaffolds

Templates can also add features to existing projects:

Tell Claude:

"Add a feature scaffold to my-react-template that adds a test file 
to an existing component"

Claude uses generate-feature-scaffold:

This adds a new feature configuration to your scaffold.yaml:

# Added to templates/my-react-template/scaffold.yaml
features:
  - name: add-component-tests
    description: Add test file to existing component

    variables_schema:
      type: object
      properties:
        componentName:
          type: string
          description: Existing component name
          example: Button
      required:
        - componentName
      additionalProperties: false

    includes:
      - src/{{ componentName }}/{{ componentName }}.test.tsx

    # File patterns this feature works with (for documentation)
    patterns:
      - src/**/Component.tsx

Now you can retroactively add tests:

scaffold-mcp scaffold add add-component-tests \
  --project ./packages/Card \
  --vars '{"componentName":"Card"}'

Advanced: Custom Generators

For complex scenarios, you can write TypeScript generators with full programmatic control.

When to Use Custom Generators

Use a custom generator when you need:

  • Complex path transformations: Converting /dashboard/users/[id] into nested folders
  • Project analysis: Reading existing files to make decisions
  • Dynamic file generation: Creating variable numbers of files
  • Custom validation: Logic beyond JSON Schema

Basic Generator Example

// templates/my-template/generators/pageGenerator.ts

import { 
  GeneratorContext, 
  ScaffoldResult,
  ScaffoldProcessingService 
} from '@agiflowai/scaffold-generator';
import path from 'node:path';

interface PageVariables {
  pagePath: string;
  pageTitle: string;
  withLayout?: boolean;
}

const generate = async (context: GeneratorContext): Promise<ScaffoldResult> => {
  const { 
    variables, 
    fileSystem, 
    variableReplacer,
    templatePath, 
    targetPath 
  } = context;
  
  const vars = variables as PageVariables;
  
  // Custom logic: Convert /dashboard/users to nested folders
  const segments = vars.pagePath.split('/').filter(Boolean);
  const pageFolderPath = path.join(targetPath, 'src/app', ...segments);
  
  // Check if route already exists
  const exists = await fileSystem.exists(path.join(pageFolderPath, 'page.tsx'));
  if (exists) {
    return {
      success: false,
      message: `Page already exists at ${vars.pagePath}`
    };
  }
  
  // Process template files
  const processingService = new ScaffoldProcessingService(
    fileSystem,
    variableReplacer
  );
  
  const createdFiles: string[] = [];
  
  // Copy page.tsx template
  await processingService.copyAndProcess(
    path.join(templatePath, 'src/page/page.tsx.liquid'),
    path.join(pageFolderPath, 'page.tsx'),
    variables,
    createdFiles
  );
  
  // Conditionally copy layout.tsx
  if (vars.withLayout) {
    await processingService.copyAndProcess(
      path.join(templatePath, 'src/page/layout.tsx.liquid'),
      path.join(pageFolderPath, 'layout.tsx'),
      variables,
      createdFiles
    );
  }
  
  return {
    success: true,
    message: `Successfully created page at /${vars.pagePath}`,
    createdFiles
  };
};

export default generate;

Reference your generator in scaffold.yaml:

features:
  - name: scaffold-nextjs-page
    description: Add a new Next.js App Router page
    generator: pageGenerator.ts  # ← Use custom generator
    
    variables_schema:
      type: object
      properties:
        pagePath:
          type: string
          description: "Page route path (e.g., 'dashboard/users')"
          example: "dashboard/users"
        pageTitle:
          type: string
          description: "Page title for metadata"
        withLayout:
          type: boolean
          description: "Include layout.tsx file"
          default: false
      required:
        - pagePath
        - pageTitle

Real-World Results

Organizations using scaffold-mcp report dramatic improvements:

Before Scaffolding

  • Setup time: 2-3 hours per new project
  • Code consistency: 60-70% (many variations)
  • Pattern reuse: Manual copy-paste with modifications
  • Review time: 30-45 minutes per component (style debates)
  • Onboarding: 2 weeks to learn team conventions

After Scaffolding

  • Setup time: 2-3 minutes per new project
  • Code consistency: 100% (enforced by templates)
  • Pattern reuse: Automated with templates
  • Review time: 5-10 minutes per component (logic only)
  • Onboarding: 2 days with template documentation

Net result: 10x faster project initialization, zero convention debates, consistent quality.

Best Practices

1. Start with Provided Templates, Customize Gradually

Don't try to create perfect templates on day one:

# Week 1: Use provided templates
scaffold-mcp add --name nextjs-15 --url https://github.com/AgiFlow/aicode-toolkit

# Week 2-4: Observe what you change repeatedly

# Week 5+: Create custom templates for your modifications

2. Use Liquid Filters for Consistency

Scaffold-mcp provides powerful filters for case transformations:

{% comment %}
✅ Good: Ensure consistent casing with filters
Available filters: pascalCase, camelCase, kebabCase, snakeCase, upperCase, pluralize, singularize
{% endcomment %}
export const {{ componentName | pascalCase }} = () => {
  const className = "{{ componentName | kebabCase }}";
  const constant = {{ componentName | upperCase }}_VALUE;
};

{% comment %}
❌ Bad: Rely on user input casing
{% endcomment %}
export const {{ componentName }} = () => {
  const className = "{{ componentName }}";
};

3. Validate with JSON Schema

# ✅ Good: Enforce format
properties:
  componentName:
    type: string
    pattern: "^[A-Z][a-zA-Z0-9]*$"  # Must be PascalCase
    example: "UserCard"

# ❌ Bad: Accept anything
properties:
  componentName:
    type: string

4. Document with Instruction Fields

instruction: |
  Component created successfully!
  
  Files created:
  - {{ componentName }}.tsx: Main component
  - {{ componentName }}.test.tsx: Test file
  - {{ componentName }}.stories.tsx: Storybook story
  
  Next steps:
  1. Run `pnpm test` to verify tests pass
  2. Run `pnpm storybook` to view component
  3. Import in your app: import { {{ componentName }} } from '@/components/{{ componentName }}'
  
  Design patterns:
  - Component uses Tailwind CSS utility classes
  - Props interface is exported for reuse
  - Component is fully typed with TypeScript

5. Test with Different Variable Combinations

Create test cases in your template README:

# Test Case 1: Minimal
variables:
  componentName: Button

# Test Case 2: All features
variables:
  componentName: UserProfile
  withTests: true
  withStorybook: true
  withStyles: true

# Test Case 3: Edge cases
variables:
  componentName: My-Component-Name  # Tests kebabCase filter
  description: ""  # Tests empty string handling

Getting Started Today

Ready to transform your AI-assisted development?

Quick Start (5 minutes)

# 1. Install globally
npm install -g @agiflowai/scaffold-mcp
# or locally in your project
pnpm install @agiflowai/scaffold-mcp

# 2. Initialize templates folder
scaffold-mcp init

# 3. Add a template from repository (optional)
scaffold-mcp add --name nextjs-15 --url https://github.com/AgiFlow/aicode-toolkit

# 4. List available boilerplates
scaffold-mcp boilerplate list

# 5. Create a project
scaffold-mcp boilerplate create <name> --vars '{"projectName":"my-app"}'

Claude Code Setup (2 minutes)

Add to your Claude Code config:

{
  "mcpServers": {
    "scaffold-mcp": {
      "command": "npx",
      "args": ["-y", "@agiflowai/scaffold-mcp", "mcp-serve"]
    }
  }
}

Restart Claude Code and say: "What scaffolding templates are available?"

For Template Creators

Enable admin tools:

{
  "mcpServers": {
    "scaffold-mcp": {
      "command": "npx",
      "args": ["-y", "@agiflowai/scaffold-mcp", "mcp-serve", "--admin-enable"]
    }
  }
}

Then tell Claude: "Help me create a new boilerplate template for my team's component pattern"

The Path Forward

The future of AI-assisted development isn't about AI writing more code—it's about AI writing the right code, consistently, following your conventions.

Intelligent scaffolding transforms AI from a tool that generates code into a teammate that understands and respects your project's architecture, patterns, and conventions.

Three Levels of Adoption

Level 1: User (Start here)

  • Use existing templates from the community
  • 10x faster project setup
  • Guaranteed consistency

Level 2: Customizer (Next step)

  • Adapt templates to your team's conventions
  • Encode your patterns once, reuse forever
  • Zero convention debates

Level 3: Creator (Advanced)

  • Build custom templates for your tech stack
  • Advanced generators for complex workflows
  • Share templates across your organization

The Bottom Line

Stop fighting with AI over conventions. Stop reviewing the same style issues. Stop recreating the same patterns.

Start with templates. Scale with scaffolding.

Resources

- Main README - Project overview and philosophy - Getting Started Guide - Installation and usage - Template Conventions - Complete Liquid syntax guide - Advanced Generators - Custom TypeScript generators - CLI Commands Reference - All CLI commands - MCP Tools Reference - MCP server tools

---

"The best code is the code you don't have to write. But when you do write it, scaffolding ensures you write it right the first time—every time."

Ready to scale your AI-assisted development? Install @agiflowai/scaffold-mcp and start building with consistency today.