Pular para o conteúdo
Voltar

Como Estruturar Projetos Fullstack para Desenvolvimento Assistido por IA

Publicado:  at  07:00 AM

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:

Arquitetura:

Convenções de código:

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:

  1. .ai/ fica no topo ao ordenar alfabeticamente (convenção Unix de dot folders)
  2. Deixa claro que é material de trabalho para IA, não documentação de usuário
  3. 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

/agents/ - Instruções específicas por domínio

/context/ - Decisões arquiteturais

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:

  1. Decisão de alterar feature X
  2. Atualizar .ai/prd/feature-x.md
  3. Pedir à IA para implementar conforme novo PRD

Fluxo errado:

  1. Pedir à IA pra mudar feature X
  2. IA implementa baseada em contexto antigo
  3. Você explica a mudança no prompt
  4. PRD fica desatualizado
  5. 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:

  1. Ela atualiza packages/types/api.ts
  2. Atualiza o backend automaticamente (já importa de lá)
  3. Atualiza o frontend automaticamente (já importa de lá)
  4. 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:

  1. packages/utils/validation/user.ts e adiciona o campo no schema Zod
  2. packages/types/api.ts e adiciona nos tipos
  3. prisma/schema.prisma e adiciona no model User
  4. Gera migration
  5. Atualiza rota do backend
  6. Atualiza form do frontend

Tudo porque ela tem visibilidade do projeto inteiro.

Ferramentas

Uso Turborepo, mas Nx funciona igualmente bem. O importante é ter:

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:

Pode complicar em:

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:

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:

  1. Adiciona model em prisma/schema.prisma
  2. Adiciona relation em User e Post
  3. 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:

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:

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:

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:

O que não funciona:

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:

  1. Crie a pasta .ai/ hoje mesmo
  2. Documente seus guard rails básicos (linguagem, framework, convenções)
  3. Escreva o PRD da próxima feature antes de pedir à IA pra implementar
  4. Teste workflows simples primeiro (uma feature pequena de ponta a ponta)
  5. 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.



Artigo Anterior
Método GTD (Getting Things Done)
Próximo Artigo
Protótipo navegável de SaaS em 3 horas com Claude Code e Subagentes: Acertos e Erros