Introdução
Essa é a estrutura que venho usando nos meus projetos e tem funcionado bem. Não é a única forma de fazer, e provavelmente não é a melhor para todos os tipos de projeto. O que funciona para uma aplicação SaaS pode não fazer sentido para um sistema embarcado ou um projeto de data science.
Dito isso, se você está desenvolvendo aplicações fullstack com auxílio de IA, principalmente usando ferramentas como Claude, Cursor, ou Windsurf, essa abordagem pode te poupar muita dor de cabeça. A ideia é compartilhar o que aprendi errando e acertando, e você adapta pro seu contexto.
O ponto central é simples: estrutura ruim para humanos é estrutura ruim para IA. Só que com um agravante: quando a IA se perde, ela não pergunta. Ela inventa. E aí você descobre três commits depois que metade do código está seguindo um padrão e a outra metade outro completamente diferente.
Subagentes: A Fundação de Tudo
Antes de falar em estrutura de pastas, monorepo ou qualquer coisa do tipo, precisamos falar de subagentes. Essa é a fase mais crucial do projeto, e se você errar aqui, vai pagar o preço lá na frente em refatorações massivas, consumo absurdo de tokens e pontos de desestruturação do projeto.
Subagentes são funcionalidades presentes no Claude (Projects com custom instructions), mas também em outras ferramentas como Cursor (rules), Windsurf (cascade rules), e até no ChatGPT (custom GPTs). A nomenclatura varia, mas o conceito é o mesmo: você define instruções específicas que a IA deve seguir no contexto daquele projeto.
Por que isso é crucial?
Lembra do artigo sobre guard rails? É aqui que você define isso. Nomenclatura, arquitetura, padrões de código, estrutura de pastas, convenções de commit. Tudo que você não definir agora, a IA vai inventar. E ela é criativa demais para o nosso próprio bem.
O problema não é a IA fazer escolhas ruins. O problema é ela fazer escolhas diferentes a cada interação. Num dia ela cria componentes em PascalCase, no outro em kebab-case. Num momento usa named exports, no outro default exports. E quando você percebe, tem um frankenstein arquitetural nas mãos.
O que colocar nos subagentes?
Guard rails técnicos:
- Linguagens e versões (TypeScript 5.x, Node 20+)
- Frameworks e suas convenções (Next.js App Router, React Server Components)
- Bibliotecas obrigatórias (Zod para validação, Prisma para banco, etc)
- Padrões de nomenclatura (camelCase para variáveis, PascalCase para componentes)
Arquitetura:
- Estrutura de pastas esperada
- Separação de responsabilidades (onde vai lógica de negócio, onde vai UI)
- Padrões de importação (paths absolutos vs relativos)
- Organização de tipos e interfaces
Convenções de código:
- Preferências de sintaxe (arrow functions vs function declarations)
- Tratamento de erros
- Logging e debugging
- Comentários (quando e como)
Exemplos práticos:
# Frontend Agent
## Stack
- Next.js 14 (App Router)
- TypeScript 5.x
- Tailwind CSS
- shadcn/ui
## Convenções
- Componentes em PascalCase
- Arquivos de componentes com extensão .tsx
- Server Components por padrão, 'use client' apenas quando necessário
- Props sempre tipadas com interface (nunca type)
## Estrutura
- `/app` para rotas
- `/components` para componentes reutilizáveis
- `/lib` para utilitários
- Imports absolutos usando @/
# Backend Agent
## Stack
- Node.js 20+
- Fastify
- Prisma ORM
- PostgreSQL
## Convenções
- Rotas seguem padrão REST
- Validação com Zod em todas as rotas
- Erros sempre no formato { error: string, details?: unknown }
- Logs estruturados com pino
## Estrutura
- `/routes` para definição de rotas
- `/services` para lógica de negócio
- `/repositories` para acesso a dados
- Nunca misturar lógica de negócio nas rotas
O custo de não fazer isso direito
Já trabalhei em projetos onde pulei essa etapa pensando “vou definindo conforme preciso”. Resultado: na terceira feature, a IA estava criando estruturas completamente diferentes das duas primeiras. Tive que fazer uma refatoração que consumiu dois dias e uns 500k tokens só pra padronizar o que já existia.
Retrabalho não é só tempo perdido. É contexto perdido, é risco de bugs, é confusão mental. E com IA, é literalmente dinheiro saindo do bolso em tokens.
Instruções que economizam retrabalho
Além dos guard rails, existe uma categoria de instruções que fazem a diferença brutal na qualidade do código gerado: preferências de ferramentas e bibliotecas.
A IA tem uma tendência natural de reinventar a roda. Ela vai criar um componente de modal do zero quando você já tem shadcn/ui instalado. Vai escrever migrations SQL manualmente quando seu framework tem comandos específicos pra isso. Vai implementar autenticação JWT na mão quando você poderia usar NextAuth.
Comandos de framework:
Se você está usando Rails, Django, Laravel, ou qualquer framework com CLI robusta, deixe isso explícito:
## Criação de arquivos
**SEMPRE usar comandos do framework:**
- Models: `rails generate model User name:string email:string`
- Controllers: `rails generate controller Users index show`
- Migrations: `rails generate migration AddRoleToUsers role:string`
**NUNCA criar esses arquivos manualmente**
Para Next.js com Prisma:
## Database
- Migrations: sempre usar `npx prisma migrate dev --name <nome_descritivo>`
- NUNCA editar arquivos de migration gerados
- NUNCA criar arquivos SQL manualmente
- Schema changes sempre no `schema.prisma` seguido de migration
Bibliotecas especializadas:
Deixe claro quais problemas já têm solução pronta:
## Bibliotecas obrigatórias
- **Validação**: Zod (nunca validação manual)
- **Datas**: date-fns (nunca moment.js, nunca manipulação manual)
- **Forms**: react-hook-form + zod resolver
- **Tabelas**: TanStack Table (nunca implementação custom)
- **Modals/Dialogs**: shadcn/ui Dialog
- **Toast notifications**: sonner
- **Estado global**: Zustand (apenas quando necessário, preferir server state)
**Se a task envolver qualquer dessas áreas, USE a biblioteca. Não reimplemente.**
Por que isso importa:
Sem essas instruções, a IA vai pelo caminho que parece mais direto no momento. Criar um arquivo .rb manualmente parece simples, mas você perde validações automáticas, convenções do framework, e integração com outras ferramentas.
Pior: quando você precisa adicionar um campo depois, a IA não vai saber que tem que gerar uma migration. Ela vai só editar o arquivo, e você descobre o problema em produção.
Como estruturar essas instruções
Crie seções específicas no seu subagente:
# Backend Agent
## Stack
[...]
## Convenções
[...]
## Comandos e ferramentas
### Database
- ORM: Prisma
- Migrations: `npx prisma migrate dev`
- Seed: `npx prisma db seed`
### Criação de arquivos
- Routes: criar em `/routes/<recurso>.ts`
- Services: criar em `/services/<recurso>.service.ts`
- NUNCA usar geradores automáticos de boilerplate
### Bibliotecas para problemas comuns
- Validação: Zod
- Logging: pino
- Testing: vitest + supertest
- Dates: date-fns
- Slugs: slugify
- UUIDs: crypto.randomUUID() (nativo Node 20+)
**Regra de ouro: se existe biblioteca consolidada, use. Não reimplemente.**
O investimento de 30 minutos definindo isso no início vai te poupar dias de refatoração depois.
PRD para Agentes: Não é Documentação, é Instrução
PRD (Product Requirements Document) tradicional é escrito para humanos. Tem contexto de negócio, justificativas, considerações de UX. É importante, mas não é o que a IA precisa.
Para agentes, PRD é especificação técnica executável. Quanto mais específico, melhor. Abstração e flexibilidade são inimigas aqui.
Diferença na prática
PRD tradicional:
## Feature: Sistema de Notificações
### Objetivo
Melhorar o engajamento dos usuários através de notificações contextuais.
### Requisitos
- Usuários devem receber notificações relevantes
- Sistema deve ser escalável
- UX não intrusiva
PRD para IA:
## Feature: Sistema de Notificações
### Stack
- Implementar usando Server-Sent Events (SSE)
- Backend: rota `/api/notifications/stream`
- Frontend: hook `useNotifications()` em `/lib/hooks/use-notifications.ts`
- Persistência: tabela `notifications` no Prisma schema
### Schema
```prisma
model Notification {
id String @id @default(cuid())
userId String
type NotificationType
title String
message String
read Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId, read])
}
enum NotificationType {
INFO
WARNING
SUCCESS
ERROR
}
```
### Endpoints
**GET /api/notifications/stream**
- SSE endpoint
- Autenticação obrigatória via session
- Retorna eventos no formato: `data: {"id":"...","type":"INFO","title":"...","message":"..."}\n\n`
- Heartbeat a cada 30s
**GET /api/notifications**
- Lista notificações do usuário
- Query params: `?limit=20&offset=0&unreadOnly=false`
- Response: `{ notifications: Notification[], total: number }`
**PATCH /api/notifications/:id/read**
- Marca notificação como lida
- Body: `{ read: boolean }`
- Response: `{ success: boolean }`
### Frontend
**Hook useNotifications()**
```typescript
interface UseNotificationsReturn {
notifications: Notification[];
unreadCount: number;
markAsRead: (id: string) => Promise<void>;
isConnected: boolean;
}
```
**Componente NotificationBell**
- Localização: `/components/notifications/notification-bell.tsx`
- Props: nenhuma (usa hook internamente)
- Comportamento: badge com contador de não lidas, dropdown com lista ao clicar
### Regras de negócio
- Notificações expiram após 30 dias (implementar cron job)
- Máximo 100 notificações não lidas por usuário
- Quando atingir limite, deletar mais antigas automaticamente
Viu a diferença? O segundo não deixa margem pra interpretação. A IA sabe exatamente o que criar, onde criar, e como implementar.
Onde guardar o PRD
Aqui entra a estrutura de pastas. Eu uso uma pasta .ai/ na raiz do projeto:
my-project/
├── .ai/
│ ├── prd/
│ │ ├── notifications.md
│ │ ├── authentication.md
│ │ └── dashboard.md
│ ├── agents/
│ │ ├── frontend.md
│ │ ├── backend.md
│ │ └── database.md
│ └── context/
│ ├── architecture.md
│ ├── conventions.md
│ └── decisions.md
├── apps/
│ ├── web/
│ └── api/
├── packages/
└── ...
Por que .ai/ e não /docs/?
Motivos práticos:
.ai/fica no topo ao ordenar alfabeticamente (convenção Unix de dot folders)- Deixa claro que é material de trabalho para IA, não documentação de usuário
- Pode ser facilmente ignorado em builds sem configuração extra
Alguns preferem /docs/ai/, funciona também. O importante é ser consistente.
Estrutura dentro de .ai/
/prd/ - Um arquivo markdown por feature
- Nome descritivo:
notifications.md, nãofeature-1.md - Sempre com schema de dados, endpoints, e regras de negócio
- Atualizar conforme feature evolui
/agents/ - Instruções específicas por domínio
frontend.md- guard rails do frontendbackend.md- guard rails do backenddatabase.md- convenções de schema e migrations
/context/ - Decisões arquiteturais
architecture.md- visão geral do sistemaconventions.md- padrões gerais do projetodecisions.md- ADRs (Architecture Decision Records) resumidos
Como a IA usa isso
Quando você está trabalhando numa feature, o prompt fica:
Leia .ai/agents/frontend.md e .ai/prd/notifications.md
Implemente o componente NotificationBell conforme especificado no PRD.
A IA tem contexto completo: guard rails técnicos + especificação da feature. Zero ambiguidade.
Manutenção do PRD
PRDs não são estáticos. Feature muda, requisitos evoluem. Atualize o PRD antes de pedir mudanças à IA.
Fluxo correto:
- Decisão de alterar feature X
- Atualizar
.ai/prd/feature-x.md - Pedir à IA para implementar conforme novo PRD
Fluxo errado:
- Pedir à IA pra mudar feature X
- IA implementa baseada em contexto antigo
- Você explica a mudança no prompt
- PRD fica desatualizado
- Próxima mudança, IA não sabe qual é a versão atual
O PRD é a source of truth. Trate ele como código.
Monorepo: Contexto Compartilhado
Por que isso importa no contexto de desenvolvimento com IA?
Contexto compartilhado é poder.
Quando frontend e backend estão em repositórios separados, a IA trabalha cega. Ela está implementando uma API no backend sem saber exatamente como o frontend vai consumir. Ou está criando um componente no frontend sem ver os tipos reais que a API retorna.
Resultado: você vira o integrador manual. Ajustando tipos, corrigindo contratos, sincronizando mudanças.
Estrutura que funciona
my-project/
├── .ai/ # Instruções e PRDs
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── app/
│ │ ├── components/
│ │ └── lib/
│ └── api/ # Fastify backend
│ ├── routes/
│ ├── services/
│ └── repositories/
├── packages/
│ ├── types/ # Tipos compartilhados
│ │ ├── api.ts
│ │ ├── database.ts
│ │ └── index.ts
│ ├── config/ # Configs compartilhadas
│ │ ├── eslint/
│ │ └── typescript/
│ └── utils/ # Utilitários compartilhados
│ ├── validation/
│ └── formatting/
├── prisma/
│ ├── schema.prisma
│ └── migrations/
├── package.json # Root package
└── turbo.json # Turborepo config
Por que isso ajuda a IA
1. Tipos compartilhados
Arquivo packages/types/api.ts:
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
export interface CreateUserRequest {
email: string;
name: string;
password: string;
}
export interface CreateUserResponse {
user: User;
token: string;
}
Backend usa:
import { CreateUserRequest, CreateUserResponse } from '@repo/types'
app.post<{ Body: CreateUserRequest, Reply: CreateUserResponse }>('/users', ...)
Frontend usa:
import { CreateUserResponse } from '@repo/types'
const response = await fetch('/api/users', ...)
const data: CreateUserResponse = await response.json()
A IA vê os dois lados. Quando você pede pra ela adicionar um campo role no usuário:
- Ela atualiza
packages/types/api.ts - Atualiza o backend automaticamente (já importa de lá)
- Atualiza o frontend automaticamente (já importa de lá)
- Atualiza o schema do Prisma
Tudo em uma interação. Zero dessincronização.
2. Validação compartilhada
packages/utils/validation/user.ts:
import { z } from "zod";
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
password: z.string().min(8),
});
export type CreateUserInput = z.infer<typeof createUserSchema>;
Backend valida:
import { createUserSchema } from "@repo/utils/validation";
const body = createUserSchema.parse(request.body);
Frontend valida (react-hook-form):
import { createUserSchema } from "@repo/utils/validation";
import { zodResolver } from "@hookform/resolvers/zod";
const form = useForm({
resolver: zodResolver(createUserSchema),
});
Mesmas regras, zero duplicação, zero chance de divergência.
3. IA entende o fluxo completo
Prompt:
Adicione campo `phoneNumber` opcional no cadastro de usuários.
A IA:
- Lê
packages/utils/validation/user.tse adiciona o campo no schema Zod - Lê
packages/types/api.tse adiciona nos tipos - Lê
prisma/schema.prismae adiciona no model User - Gera migration
- Atualiza rota do backend
- Atualiza form do frontend
Tudo porque ela tem visibilidade do projeto inteiro.
Ferramentas
Uso Turborepo, mas Nx funciona igualmente bem. O importante é ter:
- Workspace compartilhado (
pnpm workspaceounpm workspaces) - Build orchestration (Turbo ou Nx)
- Caching de builds
Configuração básica turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^lint"]
}
}
}
Quando monorepo complica
Honestidade: monorepo não é bala de prata.
Não use se:
- Times completamente separados (frontend e backend em empresas diferentes)
- Deploys em ritmos muito diferentes (mobile release mensal vs backend deploy contínuo)
- Tecnologias muito heterogêneas (não faz sentido mono com Go backend + React Native + Python ML)
Pode complicar em:
- CI/CD inicial (precisa configurar affected builds)
- Permissões (se precisa de acesso granular por time)
- Onboarding (projeto parece maior do que é)
Mas para desenvolvimento solo ou times pequenos fazendo fullstack com IA? Vale muito a pena.
Como Lidar com Contexto Grande
Projetos crescem. Instruções aumentam. Uma hora você bate no limite de contexto da IA, e aí? Tem estratégias melhores que “rezar pra caber”.
O problema
Você começa com um arquivo frontend.md de 200 linhas. Adiciona mais convenções, mais exemplos, mais edge cases. Seis meses depois tem 2000 linhas de instruções. A IA começa a:
- Ignorar partes do final
- Confundir instruções de seções diferentes
- Consumir contexto demais, sobrando pouco pra código
- Ficar lenta pra processar tudo
Estratégia 1: Splitting por domínio
Ao invés de um frontend.md gigante, quebre em responsabilidades específicas:
.ai/
├── agents/
│ ├── frontend/
│ │ ├── components.md # Convenções de componentes
│ │ ├── routing.md # Next.js App Router
│ │ ├── state.md # Zustand, server state
│ │ ├── forms.md # react-hook-form + zod
│ │ └── styling.md # Tailwind, shadcn/ui
│ ├── backend/
│ │ ├── api.md # REST conventions
│ │ ├── auth.md # NextAuth config
│ │ ├── database.md # Prisma, migrations
│ │ └── validation.md # Zod schemas
│ └── shared/
│ ├── types.md # TypeScript conventions
│ └── testing.md # Vitest, Playwright
No prompt, você referencia apenas o que precisa:
Leia .ai/agents/frontend/components.md e .ai/agents/frontend/forms.md
Crie um formulário de cadastro de produto conforme PRD.
A IA não processa 2000 linhas. Processa 400 das seções relevantes.
Estratégia 2: Hierarquia de instruções
Organize em níveis de especificidade:
.ai/agents/core.md - Regras que aplicam a TODO o projeto
# Core Conventions
- TypeScript strict mode sempre ativo
- Nunca usar `any`, sempre tipar corretamente
- Imports absolutos com @/ (Next.js) ou @repo/ (monorepo)
- Comentários em português apenas quando lógica complexa exigir
- Error handling: sempre capturar e logar erros estruturados
.ai/agents/frontend/base.md - Regras gerais de frontend
# Frontend Base
- Server Components por padrão
- 'use client' apenas quando necessário (interatividade, hooks, context)
- Async/await em Server Components, nunca useEffect para fetch
.ai/agents/frontend/forms.md - Específico de forms
# Forms
- react-hook-form + zod resolver
- Validação client-side e server-side (mesma schema)
- Loading states durante submit
- Toast de sucesso/erro (sonner)
No prompt:
Contexto:
- .ai/agents/core.md (sempre)
- .ai/agents/frontend/base.md
- .ai/agents/frontend/forms.md
Task: implementar form de login
Você carrega apenas o necessário, mas mantém consistência via core.md.
Estratégia 3: Instruções dinâmicas por feature
Para features grandes, crie instruções específicas temporárias:
.ai/
├── agents/
├── prd/
│ └── notifications/
│ ├── spec.md # PRD principal
│ ├── implementation.md # Detalhes técnicos
│ └── sse-guide.md # Guia de SSE (temporário)
sse-guide.md existe só durante implementação de notifications. Depois vira parte de backend/realtime.md se você for usar SSE em outras features.
Estratégia 4: Referências ao invés de duplicação
Evite repetir a mesma informação em múltiplos arquivos:
❌ Errado:
# frontend/components.md
[500 linhas sobre shadcn/ui]
# frontend/forms.md
[mesmas 500 linhas sobre shadcn/ui]
✅ Certo:
# frontend/components.md
[500 linhas sobre shadcn/ui]
# frontend/forms.md
Para convenções de UI components, veja components.md
## Forms específicos
- Sempre usar Form, FormField, FormItem de shadcn/ui
- [...]
A IA é boa em seguir referências. Você pode dizer “veja X.md para detalhes de Y” e ela vai buscar.
Estratégia 5: Exemplos em arquivos separados
Exemplos de código podem ocupar muito espaço. Extraia:
.ai/
├── agents/
│ └── frontend/
│ ├── components.md
│ └── examples/
│ ├── server-component.tsx
│ ├── client-component.tsx
│ └── form-example.tsx
Em components.md:
# Componentes
## Server Components
Para exemplo completo, veja examples/server-component.tsx
## Client Components
Para exemplo completo, veja examples/client-component.tsx
Você referencia os exemplos só quando necessário.
Estratégia 6: Versionamento de instruções
Projetos pivotam. Arquitetura muda. Não delete instruções antigas, versione:
.ai/
├── agents/
│ └── frontend/
│ ├── state.md # Versão atual
│ └── archive/
│ └── state-v1-redux.md # Quando usávamos Redux
Se precisar referenciar decisões antigas ou entender migrações, está lá.
Regra de ouro
Se uma instrução não foi referenciada nos últimos 10 prompts, provavelmente não precisa estar no contexto padrão.
Revise periodicamente. Extraia o que não está sendo usado. Mantenha apenas o essencial no “core”, e o resto sob demanda.
Workflows Práticos: Do Prompt à Implementação
Teoria é legal, mas como isso funciona no dia a dia? Vou mostrar workflows reais que uso.
Workflow 1: Nova feature do zero
Contexto: Adicionar sistema de favoritos em posts
Passo 1: Atualizar PRD
# Criar ou atualizar
.ai/prd/favorites.md
Conteúdo:
# Feature: Sistema de Favoritos
## Database Schema
```prisma
model Favorite {
id String @id @default(cuid())
userId String
postId String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
post Post @relation(fields: [postId], references: [id])
@@unique([userId, postId])
@@index([userId])
}
```
## API Endpoints
**POST /api/posts/:postId/favorite**
- Toggle favorite (adiciona se não existe, remove se existe)
- Auth obrigatória
- Response: `{ favorited: boolean }`
**GET /api/users/me/favorites**
- Lista posts favoritados do usuário
- Query: `?limit=20&offset=0`
- Response: `{ posts: Post[], total: number }`
## Frontend
**Hook useFavorite(postId: string)**
```typescript
interface UseFavoriteReturn {
isFavorited: boolean;
toggleFavorite: () => Promise<void>;
isLoading: boolean;
}
```
**Componente FavoriteButton**
- Props: `{ postId: string, size?: 'sm' | 'md' | 'lg' }`
- Ícone: star outline quando não favoritado, star filled quando favoritado
- Optimistic update (muda UI antes da resposta)
- Toast de erro se falhar
Passo 2: Implementação - Database
Prompt:
Contexto:
- .ai/agents/backend/database.md
- .ai/prd/favorites.md
Task: Adicionar model Favorite ao Prisma schema e gerar migration
IA executa:
- Adiciona model em
prisma/schema.prisma - Adiciona relation em
UserePost - Roda
npx prisma migrate dev --name add-favorites
Passo 3: Implementação - Backend
Prompt:
Contexto:
- .ai/agents/backend/api.md
- .ai/agents/backend/validation.md
- .ai/prd/favorites.md
Task: Implementar endpoints de favoritos conforme PRD
IA cria:
apps/api/routes/favorites.tsapps/api/services/favorites.service.ts- Validação Zod se necessário
- Testes básicos
Passo 4: Implementação - Frontend
Prompt:
Contexto:
- .ai/agents/frontend/base.md
- .ai/agents/frontend/components.md
- .ai/prd/favorites.md
Task: Implementar hook useFavorite e componente FavoriteButton
IA cria:
apps/web/lib/hooks/use-favorite.ts(com SWR ou TanStack Query)apps/web/components/posts/favorite-button.tsx- Optimistic updates
- Error handling
Passo 5: Integração
Prompt:
Adicione FavoriteButton no PostCard component.
Local: apps/web/components/posts/post-card.tsx
IA adiciona o botão no lugar certo, passa props corretas.
Passo 6: Teste manual
Você testa. Encontra um bug: favoritar posts duplica na lista de favoritos.
Passo 7: Debug
Prompt:
Bug: ao favoritar um post, ele aparece duplicado em /favorites
Contexto relevante:
- apps/web/app/favorites/page.tsx
- apps/web/lib/hooks/use-favorite.ts
Investigue e corrija.
IA identifica: optimistic update não está checando se post já existe na lista. Corrige.
Workflow 2: Refatoração guiada
Contexto: Migrar de Redux pra Zustand
Passo 1: Documentar decisão
Criar .ai/context/decisions.md ou adicionar entry:
## 2025-02-08: Migração Redux → Zustand
### Motivo
- Redux muito verboso para cases simples
- Zustand mais leve e fácil manutenção
- Server state já usa TanStack Query, Redux só pra UI state pequeno
### Estratégia
- Migrar store por store
- Começar por `userPreferences` (menor)
- Depois `sidebar`, `modals`
- `cart` por último (mais complexo)
Passo 2: Atualizar instruções de agent
Mover .ai/agents/frontend/state.md pra .ai/agents/frontend/archive/state-v1-redux.md
Criar novo .ai/agents/frontend/state.md:
# State Management
## Client State
- Zustand para UI state
- Stores em `/lib/stores/<feature>.ts`
- Sempre tipar state e actions
## Server State
- TanStack Query
- Queries em `/lib/queries/<feature>.ts`
- Mutations co-localizadas
Passo 3: Migração incremental
Prompt:
Contexto:
- .ai/agents/frontend/state.md
- .ai/context/decisions.md (seção migração Redux)
Task: Migrar store userPreferences de Redux pra Zustand
Arquivos atuais:
- apps/web/store/slices/user-preferences.ts (Redux)
- apps/web/hooks/use-preferences.ts (selector hook)
Criar:
- apps/web/lib/stores/user-preferences.ts (Zustand)
- Atualizar todos os consumidores
IA faz a migração, você testa, commit.
Repete para cada store até concluir.
Workflow 3: Implementação com subagente
Contexto: Task complexa, backend + frontend integrados
Setup: Usar Projects no Claude com custom instructions já configuradas
Prompt inicial:
Feature: Dashboard de analytics
PRD: .ai/prd/analytics-dashboard.md
Vou dividir em 3 etapas:
1. Backend (API de métricas)
2. Frontend (componentes e queries)
3. Integração e polish
Confirme que leu o PRD e entendeu o escopo antes de começarmos.
IA confirma, você prossegue etapa por etapa no mesmo chat. Contexto mantido, decisões consistentes.
Workflow 4: Feature flagging com IA
Contexto: Feature nova mas incerta, precisa de flag
Passo 1: PRD com flag
.ai/prd/new-editor.md:
# Feature: Novo Editor WYSIWYG
**Feature Flag: `new_editor_enabled`**
## Implementação
- Feature flag em `.env`: `NEXT_PUBLIC_NEW_EDITOR_ENABLED`
- Componente: `EditorV2`
- Fallback: `EditorV1` (atual)
## Frontend
```typescript
const EditorSwitch = () => {
const enabled = process.env.NEXT_PUBLIC_NEW_EDITOR_ENABLED === 'true'
return enabled ? <EditorV2 /> : <EditorV1 />
}
```
Prompt:
Contexto: .ai/prd/new-editor.md
Implementar EditorV2 com feature flag conforme PRD.
Garantir que sem flag ativa, EditorV1 continua funcionando.
IA implementa com flag, você testa incrementalmente.
Workflow 5: Pair programming com IA
Quando usar: Task ambígua, precisa de exploração
Você não tem PRD completo. Só uma ideia.
Prompt:
Quero implementar "sugestões de tags automáticas" ao criar post.
Ideia: baseado no título e conteúdo, sugerir tags relevantes.
Antes de implementar, me ajude a definir:
1. Isso deve ser client-side ou server-side?
2. Usamos IA externa (OpenAI) ou algo mais simples?
3. Como armazenar tags existentes pra sugestão?
4. UX: sugere durante digitação ou apenas ao final?
Considerando nosso stack (Next.js, Prisma, etc).
IA discute opções, você decide, atualiza PRD com decisões, aí sim implementa.
Workflow 6: Manutenção e evolução
Contexto: Feature antiga precisa de ajustes
Prompt:
Feature: Notificações (implementada há 2 meses)
Mudança necessária: adicionar categoria "MENTION" quando usuário é mencionado.
Arquivos envolvidos:
- .ai/prd/notifications.md (atualizar)
- packages/types/api.ts (adicionar enum)
- prisma/schema.prisma (atualizar enum)
- backend e frontend (aplicar mudança)
Execute em ordem:
1. Mostre diff do que vai mudar no PRD
2. Aguarde aprovação
3. Implemente mudanças
4. Gere migration
IA mostra o diff, você aprova, ela executa. PRD sempre atualizado.
Dicas gerais
1. Sempre começe com contexto
Não jogue prompts soltos. Referencie os arquivos .ai/ relevantes.
2. Divida tasks grandes IA funciona melhor com tasks atômicas. “Implementar autenticação completa” vira:
- Implementar schema de User
- Implementar hash de senha
- Implementar rotas de auth
- Implementar middleware de sessão
- Implementar UI de login
- Integrar tudo
3. Peça confirmação em pontos críticos Antes de migrations, antes de deletar código, antes de mudanças estruturais.
4. Mantenha histórico Não delete prompts antigos. Se der ruim, você volta e vê o que pediu.
5. Use Artifacts/Code Blocks Peça pra IA mostrar código antes de aplicar. Revise, então confirme.
6. Commit frequente Após cada etapa bem sucedida. Se IA errar na próxima, você reverte fácil.
7. Teste antes de prosseguir Não empilhe 5 tasks sem testar nenhuma. Erra cedo, conserta cedo.
Conclusão
Estruturar projetos pensando em IA não é sobre servir as ferramentas. É sobre criar sistemas que humanos e máquinas consigam navegar sem se perder.
A diferença entre um projeto que escala bem com IA e um que vira pesadelo de refatoração está nos primeiros 30 minutos. Nos guard rails que você define, no PRD que você escreve, na estrutura de pastas que você escolhe.
O que funciona:
- Subagentes bem configurados desde o início
- PRDs específicos, não abstratos
- Monorepo quando faz sentido (fullstack integrado)
- Contexto organizado e sob demanda
- Workflows iterativos e bem definidos
O que não funciona:
- Jogar prompts e torcer pro melhor
- Deixar a IA inventar padrões
- PRDs vagos cheios de “deve ser escalável”
- Instruções gigantes que ninguém (nem a IA) lê por completo
- Acumular dívida técnica achando que “depois eu arrumo”
Você vai errar. Eu errei muito até chegar nessa estrutura. Ainda erro. Mas agora quando erro, sei exatamente onde olhar pra corrigir. O PRD está desatualizado? As instruções do subagente estão conflitando? O contexto está grande demais?
Próximos passos se você está começando:
- Crie a pasta
.ai/hoje mesmo - Documente seus guard rails básicos (linguagem, framework, convenções)
- Escreva o PRD da próxima feature antes de pedir à IA pra implementar
- Teste workflows simples primeiro (uma feature pequena de ponta a ponta)
- Evolua conforme o projeto cresce
Não precisa implementar tudo de uma vez. Comece pequeno, sinta o que funciona pro seu contexto, ajuste.
O futuro do desenvolvimento não é IA fazendo tudo sozinha. É você orquestrando sistemas complexos com clareza, usando IA como multiplicador de força. Mas multiplicador de força só funciona se a direção está clara.
Estrutura é direção.