The Moment I Realized AI Coding Was Broken.
It was 10PM. I’d just asked Claude to add a navigation component. Thirty seconds later, I was staring at this:
// What the AI generated (again)
const Navigation = ({ items }: NavigationProps) => {
const [open, setOpen] = useState(false);
return <nav className="navigation">...</nav>
}
export default Navigation;
Nothing wrong with it, technically. Except I don’t use default exports. I use named exports. And useState should come from our custom hooks. And we use ‘isOpen’, not ‘open’. And the TypeScript interface should be exported separately like every other component in our codebase.
I’d explained this exact pattern so many times I’d lost count.
Same pattern. Different day. Different component. Different wrong impl…
The Moment I Realized AI Coding Was Broken.
It was 10PM. I’d just asked Claude to add a navigation component. Thirty seconds later, I was staring at this:
// What the AI generated (again)
const Navigation = ({ items }: NavigationProps) => {
const [open, setOpen] = useState(false);
return <nav className="navigation">...</nav>
}
export default Navigation;
Nothing wrong with it, technically. Except I don’t use default exports. I use named exports. And useState should come from our custom hooks. And we use ‘isOpen’, not ‘open’. And the TypeScript interface should be exported separately like every other component in our codebase.
I’d explained this exact pattern so many times I’d lost count.
Same pattern. Different day. Different component. Different wrong implementation.
This wasn’t a one-off. My monorepo had become a Frankenstein’s monster of inconsistent patterns—each one technically correct, all of them a maintenance nightmare.
The promise was simple: AI would code faster than humans.
The reality? I was spending more time fixing AI-generated code than I would’ve spent just writing it myself.
How AI-Assisted Development Actually Breaks
Here’s what nobody tells you about scaling with AI coding assistants:
Week 1: The Honeymoon Phase
You: “Build me a login page” AI: ✨ generates perfect login page ✨ You: “Holy shit, this is the future”
Everything works. You’re shipping features at 10x speed. Your manager thinks you’re a wizard. You’re thinking about that promotion.
Month 1: The Cracks Appear
You’re reviewing frontend components and notice something odd:
// TaskBadge.tsx (written 2 weeks ago)
export const TaskBadge = ({ status }: TaskBadgeProps) => {
return <span className={`badge ${getColor(status)}`}>{status}</span>;
};
// PriorityBadge.tsx (written yesterday)
export function PriorityBadge(props: PriorityProps) {
const color = getPriorityColor(props.priority);
return <div className={color}>{props.priority}</div>;
}
// StatusLabel.tsx (written today)
function StatusLabel({ value }: StatusProps) {
return <Badge variant={getVariant(value)}>{value}</Badge>;
}
Three badge components. Three different patterns. All from the same AI. All from the same human (you).
And on the backend, it’s the same story:
// userService.ts (2 weeks ago)
@injectable()
export class UserService {
constructor(@inject(TYPES.Database) private db: Database) {}
}
// authService.ts (yesterday)
export class AuthService {
private db: Database;
constructor(database: Database) {
this.db = database;
}
}
// paymentService.ts (today)
class PaymentService {
constructor(public database: Database) {}
}
Three services. One uses dependency injection properly. One doesn’t. One is halfway there.
“Okay, I need better instructions,” you think.
Month 2: The Documentation Death Spiral
Your CLAUDE.md
file has grown massively. You’ve documented:
- Component patterns ✓
- Import styles ✓
- File naming ✓
- Prop validation ✓
- Error handling ✓
- State management ✓
- API patterns ✓ You’ve told the AI everything.
Then you ask it to create a settings page, and it still uses a different button component than the rest of your app.
“But I literally documented this!” you scream at your screen at 3 AM.
The AI apologizes (One-time, I said “Your’re f*king right” which is hilarious). Generates a new version. Wrong again, but differently wrong.
Month 3: The Breaking Point
You’re now maintaining:
- Dozens of CLAUDE.md files scattered everywhere
- Multiple variations of what should be the same pattern
- A massive style guide that the AI follows inconsistently
- Code reviews that are mostly style debates instead of logic discussions The math breaks: You’re spending hours fixing what should’ve taken minutes to write.
This was me. And this was my monorepo:
- Frontend apps built with Next.js and TanStack Start
- Backend APIs using Hono.js, FastAPI and Lambda
- Shared packages for everything reusable
- Microservices, edge functions, and infrastructure all in one repo The bigger it grew, the worse it got. And I wasn’t alone.
My Failed Experiments
Attempt 1: The Mega CLAUDE.md
I created comprehensive documentation files referencing everything:
- Project Structure
- Coding Standards
- Technology Stack
- Conventions
- Style System
- Development Process Result: Even with token-efficient docs, I couldn’t cover all design patterns across multiple languages and frameworks. AI still made mistakes.
Attempt 2: CLAUDE.md Everywhere
“Maybe collocated instructions work better?” I created CLAUDE.md files everywhere for different apps, APIs, and packages.
Result: Slightly better when loaded in context (which didn’t always happen). But the real issue: I only had a handful of distinct patterns. Maintaining dozens of instruction files for those same patterns? Nightmare fuel.
Attempt 3: Autonomous Workflows
I set up autonomous loops: PRD → code → lint/test → fix → repeat.
Result: I spent more time removing code and fixing bugs than if I’d just coded it myself. The AI would hallucinate solutions, ignore patterns, and create technical debt faster than I could clean it up.
The Three Core Problems
1. Inconsistency Across Codebase
Frontend:
// AgentStatus.tsx - uses our design system
export const AgentStatus = ({ status }: Props) => {
return <Badge className={getStatusColor(status)}>{status}</Badge>;
};
// TaskStatus.tsx - reinvents the wheel
export function TaskStatus({ task }: TaskProps) {
return <div className="status-badge">{task.status}</div>;
}
// SessionStatus.tsx - different again
const SessionStatus = (props: SessionProps) => (
<span className={styles.badge}>{props.status}</span>
);
Backend:
// taskRepo.ts - proper DI
@injectable()
export class TaskRepository {
constructor(@inject(TYPES.Database) private db: IDatabaseService) {}
}
// projectRepo.ts - missing decorator
export class ProjectRepository {
constructor(private db: IDatabaseService) {}
}
// memberRepo.ts - no DI at all
export class MemberRepository {
private db = getDatabaseClient();
}
Same concept, different implementations. All technically correct. All maintenance nightmares.
2. Context Window Overload
Your documentation grows from this:
# Conventions
- Use functional components
- Use TypeScript
To this monstrosity:
# Conventions
## Components
- Use functional components
- Props interface must be exported
- Use PascalCase for component names
...(10+ reference docs)
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
- Badge components that look identical but use different approaches
- Repository classes with the same DI pattern but inconsistent implementation
- Service classes that all need the same base configuration Each time slightly different. Hours wasted on work already done.
The Solution: Intelligent Scaffolding
Instead of fighting these problems with longer instructions, I needed a fundamental shift: teach AI to use templates, not just write code. 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 Key Insight
Traditional scaffolding requires complete, rigid templates. But with AI coding assistants, you only need:
- A skeleton with minimal code
- A header comment declaring the pattern and rules
- Let the AI fill in the blanks contextually Example from our actual codebase:
/**
* PATTERN: Injectable Service with Dependency Injection
* - MUST use @injectable() decorator
* - MUST inject dependencies with @inject(TYPES.*)
* - MUST define constructor parameters as private/public based on usage
* - MUST include JSDoc with design principles
*/
@injectable()
export class {{ ServiceName }}Service {
constructor(
@inject(TYPES.Database) private db: IDatabaseService,
@inject(TYPES.Config) private config: Config,
) {
// AI fills in initialization logic
}
// AI generates methods following the established pattern
}
The AI now knows the rules and generates code that follows them.
Enter: Scaffold MCP
I built the @agiflowai/scaffold-mcp to implement this approach. It’s an MCP (Model Context Protocol) server that provides:
- Boilerplate templates for new projects
- Feature scaffolds for adding to existing projects
- AI-friendly minimal templates with clear patterns
Why MCP?
- ✅ Works with Claude Desktop, Cursor, or any MCP-compatible tool
- ✅ Tech stack agnostic (Next.js, React, Hono.js, your custom setup)
- ✅ Multiple modes: MCP server or standalone CLI
- ✅ Always available to AI like any other tool
Real-World Workflow Transformation
Before Scaffolding: Starting a New API
You: "Create a new Hono API with authentication"
AI: *generates files with different patterns*
You: "Wait, where's the dependency injection?"
You: "Can you use our standard middleware setup?"
You: "Actually, use Zod for validation like our other APIs..."
*Back-and-forth debugging*
After Scaffolding: Starting a New API
Using CLI:
# See available templates
scaffold-mcp boilerplate list
# Create API with exact conventions
scaffold-mcp boilerplate create hono-api-boilerplate \
--vars '{"apiName":"notification-api","port":"3002"}'
# ✓ Complete API structure created
# ✓ Dependency injection configured
# ✓ All following your team's conventions
Using Claude Desktop: Simply say: “Create a new notification API”
Claude automatically uses the scaffold-mcp MCP server and creates your API with proper DI, middleware, and validation.
Before Scaffolding: Adding Features
You: "Add a new repository class for comments"
AI: *creates class without DI decorator*
You: "No, use dependency injection like the other repos"
AI: *adds DI but forgets the @injectable decorator*
You: "Look at TaskRepository as an example"
*More back-and-forth*
After Scaffolding: Adding Features
Using CLI:
# What can I add?
scaffold-mcp scaffold list ./backend/apis/my-api
# Add matching feature
scaffold-mcp scaffold add scaffold-repository \
--project ./backend/apis/my-api \
--vars '{"entityName":"Comment","tableName":"comments"}'
# ✓ Perfect pattern match with proper DI
Using Claude Desktop: “Add a repository for comments to my API”
Claude uses scaffold-mcp to ensure the new repository matches your DI patterns, uses the correct decorators, and follows your coding standards.
Creating Your Own Templates
The real power comes from encoding your team’s patterns.
Step 1: Installation
# Install
npm install -g @agiflowai/scaffold-mcp
# Initialize templates
scaffold-mcp init
Step 2: Enable Admin Tools (Claude Desktop)
{
"mcpServers": {
"scaffold-mcp": {
"command": "npx",
"args": ["-y", "@agiflowai/scaffold-mcp", "mcp-serve", "--admin-enable"]
}
}
}
Step 3: Create Template with AI
Tell Claude:
"Create a boilerplate template called 'injectable-service' in 'backend-templates'
that creates backend services with dependency injection and proper structure"
Claude uses the admin tool to generate:
# templates/backend-templates/scaffold.yaml
boilerplate:
name: injectable-service
description: Backend service with DI, config, and proper structure
targetFolder: backend/apis/my-api/src/services
variables_schema:
type: object
properties:
serviceName:
type: string
pattern: "^[A-Z][a-zA-Z0-9]*$"
description: Service name in PascalCase
required:
- serviceName
includes:
- {{ serviceName | camelCase }}Service.ts
- {{ serviceName | camelCase }}Service.test.ts
Step 4: Create Template Files
Tell Claude:
"Create the service template file with dependency injection, config support, and JSDoc"
Claude generates:
// templates/backend-templates/{{ serviceName | camelCase }}Service.ts.liquid
/**
* {{ serviceName }} Service
*
* DESIGN PRINCIPLES:
* - Dependency Injection: Use Inversify for loose coupling
* - Type Safety: Full TypeScript support
* - Configuration: Use Config service for environment variables
*
* PATTERN: Injectable Service
* - MUST use @injectable() decorator
* - MUST inject dependencies with @inject(TYPES.*)
* - MUST document design principles in JSDoc
*/
import { inject, injectable } from 'inversify';
import type { IDatabaseService } from '@agimonai/backend-database';
import type { Config } from '@/services';
import { TYPES } from '@/types';
@injectable()
export class {{ serviceName }}Service {
constructor(
@inject(TYPES.Database) private db: IDatabaseService,
@inject(TYPES.Config) private config: Config,
) {
// Initialization logic
}
/**
* Add your service methods here
*/
public async execute() {
// Implementation
}
}
Step 5: Use Your Template
scaffold-mcp boilerplate create injectable-service \
--vars '{"serviceName":"Email"}'
# ✓ Created backend/apis/my-api/src/services/emailService.ts
# ✓ Created backend/apis/my-api/src/services/emailService.test.ts
# ✓ All with proper DI, JSDoc, and patterns
Or with Claude Desktop:
"Create a new Email service using our injectable service template"
The Results
After switching to scaffolding:
Before
- Setup time: Hours of back-and-forth per project
- Code consistency: Inconsistent across the codebase
- Review time: Mostly spent on style debates
- Onboarding: Weeks to learn all the conventions
After
- Setup time: Minutes per project
- Code consistency: Enforced by templates
- Review time: Focused on logic, not style
- Onboarding: Days instead of weeks Net result: Dramatically faster initialization, zero convention debates, consistent quality across the entire monorepo.
Best Practices
1. Start Simple, Evolve Gradually
# Week 1: Use community templates
scaffold-mcp add --name nextjs-15 --url https://github.com/AgiFlow/aicode-toolkit
# Weeks 2-4: Observe what you change repeatedly
# Week 5+: Create custom templates for your patterns
2. Use Liquid Filters for Consistency
{% comment %}
✅ Good: Ensure consistent casing with filters
Available filters: pascalCase, camelCase, kebabCase, snakeCase, upperCase
{% endcomment %}
@injectable()
export class {{ serviceName | pascalCase }}Service {
private readonly logger = createLogger('{{ serviceName | kebabCase }}');
private readonly TABLE_NAME = '{{ tableName | snakeCase }}';
}
{% comment %}
❌ Bad: Rely on user input casing
{% endcomment %}
export class {{ serviceName }}Service {
private logger = createLogger('{{ serviceName }}');
private TABLE = '{{ tableName }}';
}
3. Validate with JSON Schema
# ✅ Good: Enforce format and patterns
properties:
serviceName:
type: string
pattern: "^[A-Z][a-zA-Z0-9]*$" # Must be PascalCase
example: "Email"
port:
type: number
minimum: 3000
maximum: 9999
# ❌ Bad: Accept anything
properties:
serviceName:
type: string
port:
type: number
4. Document in Templates
instruction: |
Service created successfully!
Files created:
- {{ serviceName | camelCase }}Service.ts: Main service with DI
- {{ serviceName | camelCase }}Service.test.ts: Test suite
Next steps:
1. Register in TYPES: add {{ serviceName }}Service to dependency container
2. Run `pnpm test` to verify tests pass
3. Inject: @inject(TYPES.{{ serviceName }}Service)
Getting Started Today
Quick Start (5 minutes)
# 1. Install
npm install -g @agiflowai/scaffold-mcp
# 2. Initialize
scaffold-mcp init
# 3. List templates
scaffold-mcp boilerplate list
# 4. Create project
scaffold-mcp boilerplate create <name> --vars '{"projectName":"my-app"}'
Claude Desktop Setup (2 minutes)
{
"mcpServers": {
"scaffold-mcp": {
"command": "npx",
"args": ["-y", "@agiflowai/scaffold-mcp", "mcp-serve"]
}
}
}
Restart Claude Desktop and say: “What scaffolding templates are available?”
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.
Three Levels of Adoption
Level 1: User (Start here)
-
Use existing templates
-
10x faster setup
-
Guaranteed consistency Level 2: Customizer (Next step)
-
Adapt templates to your team
-
Encode patterns once, reuse forever
-
Zero convention debates Level 3: Creator (Advanced)
-
Build custom templates for your stack
-
Advanced generators for complex workflows
-
Share 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
- GitHub: github.com/AgiFlow/aicode-toolkit
- NPM: @agiflowai/scaffold-mcp
“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.”
This is Part 1 of my series on making AI coding assistants work on complex projects. Stay tuned for Part 2!
Questions? I’m happy to discuss architecture patterns, scaffolding strategies, or share more implementation details in the comments.