Spec-Driven Development na prática: tutorial completo de SDD em Laravel
Você abre o Claude Code, descreve a feature em três linhas, manda rodar.
Ele entrega 400 linhas. Compila. Os testes (que ele mesmo escreveu) passam. Você dá merge.
Duas semanas depois, suporte abre ticket: o relatório de vendas em PDF está somando os pedidos cancelados, o nome do arquivo não tem a data, e quem é vendedor consegue ver o faturamento de outro vendedor. O agente não inventou — você simplesmente nunca disse pra ele que essas regras existiam. É exatamente o buraco que o Spec-Driven Development existe pra fechar.
Essa cena tem nome agora. É o que o pessoal do GitHub chamou de vibe coding quando lançou o Spec Kit em 2025: pedir feature pra IA achando que ela vai adivinhar contexto que está só na sua cabeça. Spec-Driven Development (SDD) é a resposta. A spec deixa de ser comentário em PR e vira input executável: a IA gera código a partir dela, não a partir do seu prompt curto.
Neste tutorial a gente faz o ciclo completo de Spec-Driven Development em Laravel, com uma feature que parece trivial de propósito — exportar relatório de vendas em PDF — pra mostrar que SDD aparece exatamente nessas que parecem bobas. Vai ter PRD, spec.md, plan.md, tasks.md, código gerado, teste em Pest, e o ponto onde o humano ainda manda. Stack PHP, que é o nicho que ninguém cobre nesse papo.
O que é Spec-Driven Development?
Spec-Driven Development (SDD) é uma forma de desenvolver software com IA em que a especificação estruturada é escrita antes do código e passa a ser a fonte da verdade do projeto. Em vez de o agente gerar código a partir de um prompt curto, ele gera a partir de uma spec versionada, com requisitos, critérios de aceite e restrições explícitas. O ciclo típico tem quatro fases: especificar → planejar → quebrar em tasks → implementar com verificação.
Na prática, SDD é o antídoto pro vibe coding: menos alucinação de regra de negócio, mais rastreabilidade. Se você ainda está decidindo qual abordagem usar em cada situação, a gente comparou as três em agentic code vs vibe coding vs SDD: tabela de decisão.
TL;DR
- O que é: SDD é escrever a specification antes do código quando se programa com IA. A spec é a fonte da verdade do projeto, não o prompt.
- Stack/Modelos: Laravel 11, Pest 3, dompdf, Claude Code, GitHub Spec Kit (ou variação custom como o Pimzino claude-code-spec-workflow).
- Custo/Acesso: Spec Kit é open source. Claude Code requer assinatura. Tudo o que aparece neste post roda local.
- Repositório/Link útil: github.com/github/spec-kit e o ensaio de referência da Birgitta Böckeler em martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html.
O contexto: por que SDD virou pauta agora
Em 2025 o GitHub publicou o Spec Kit, a AWS lançou o Kiro, a Tessl empurrou a fronteira pra "spec-as-source", e o Thoughtworks publicou o ensaio mais lúcido sobre o assunto. Em 2026, IBM e Microsoft entraram na conversa com guias próprios e Spec-Driven Development virou padrão emergente — a busca pelo termo no Brasil multiplicou por mil em um ano.
A tese, do próprio spec-driven.md do GitHub, é direta:
"Specifications don't serve code — code serves specifications. The PRD isn't a guide for implementation; it's the source that generates implementation."
Tradução prática: o documento de requisito deixa de ser PDF morto na pasta /docs e vira input que o agente lê. O ciclo oficial do Spec Kit é Constitution → Specify → Plan → Tasks → Implement, todos comandos /speckit.* no Claude Code, gerando arquivos versionáveis em .specify/specs/[feature]/.
Mas SDD não é silver bullet. A própria Birgitta Böckeler, da Thoughtworks, aponta duas dores que dificilmente vão sumir:
"I'd rather review code than all these markdown files."
E:
"The agent ultimately not follow all the instructions."
Resumo honesto: SDD reduz drasticamente a chance do agente alucinar regra de negócio, mas não elimina o trabalho de revisar nem a não-determinismo do LLM. A gente paga essa dor com markdown. Em troca, ganha rastreabilidade que prompt curto nunca dá.
E aqui é onde o ecossistema PHP fica de fora da conversa. Os exemplos públicos de SDD são quase todos em Python, Next.js ou TypeScript. Laravel raramente aparece. Vamos consertar isso.
Pré-requisitos
- PHP 8.3, Composer, Laravel 11.
- Claude Code instalado (
npm install -g @anthropic-ai/claude-code). - Spec Kit via
uv tool install specify-cli(ou rodar comando-a-comando manualmente, como vou mostrar). - Pest 3 (
composer require pestphp/pest --dev). barryvdh/laravel-dompdfpra geração do PDF.
Toda a estrutura que vai aparecer abaixo é clonável: copia os arquivos dentro de .specify/specs/sales-report-pdf/ num projeto Laravel novo, abre o Claude Code, e o ciclo roda.
Tutorial te mostra o caminho — no Clã você constrói junto. Aula ao vivo toda semana, projetos reais de Engenharia de IA, ao lado de quem já está em produção.
Entrar no ClãA feature trivial que tem mais arestas do que parece
GET /reports/sales/export.pdf. Parece besta. Não é.
O que está implícito num pedido desses, e raramente vem no prompt:
- Filtro de período. Default? "Últimos 30 dias" ou "mês corrente"? Em qual timezone?
- Permissão. Admin enxerga tudo. Vendedor enxerga só os próprios pedidos. Como amarra com o
User? - Status do pedido. Cancelado entra no relatório? Devolvido? Em rascunho?
- Dados sensíveis. CPF do cliente vai mascarado ou cru? E se o time jurídico já bateu sobre LGPD?
- Renderização. Mais de 5.000 linhas — síncrono trava o request. Vai pra fila? Email com link?
- Nome do arquivo.
relatorio.pdfquebra UX.vendas-2026-04-01-a-2026-04-30.pdfé o que o usuário espera. - Cache. O mesmo período pedido duas vezes em dois minutos gera o PDF de novo do zero?
Vibe coding nessa feature normalmente esquece duas ou três dessas. SDD obriga a tropeçar nelas no documento, antes do código.
Mão na massa: o tutorial de SDD aplicado
Passo 1: o PRD humano (o que ainda precisa de gente)
Antes do agente entrar, alguém escreve o PRD. Foco em resultado e contexto de negócio, não em técnica. O Spec Kit chama isso de "intent".
# PRD: Exportação de relatório de vendas em PDF
## Problema
Time comercial precisa enviar relatório mensal de vendas para
contabilidade por email. Hoje exportam CSV e formatam manualmente
no Excel. Toma de 30 a 60 minutos por mês, por gerente.
## Outcome desejado
Botão "Exportar PDF" no painel de relatórios devolve o documento
no padrão visual da empresa, pronto pra enviar.
## Restrições conhecidas
- Apenas admin e gerente podem exportar.
- Vendedor só vê os próprios números, não exporta.
- LGPD: CPF do cliente sai mascarado (xxx.xxx.xxx-12).
- Pedidos cancelados entram com badge "cancelado", não somam no total.
- Período default: mês corrente, em timezone America/Sao_Paulo.
## Não-objetivos
- Não é gráfico interativo. É PDF estático.
- Não é exportação programada. É sob demanda.
Esse arquivo é o input cru. Ele não tem decisão técnica — nada de "use dompdf", nada de "endpoint REST". Isso é trabalho do /plan.
Passo 2: /specify — virar specification executável
No Claude Code, dentro do projeto Laravel:
/speckit.specify
Você cola o PRD. O agente devolve um spec.md estruturado em .specify/specs/sales-report-pdf/spec.md. A diferença é que ele explicita ambiguidades que o PRD escondeu:
# Spec: Exportação de relatório de vendas em PDF
## User Stories
US-1: Como gerente, quero exportar PDF do mês corrente em 1 clique.
US-2: Como admin, quero filtrar por intervalo arbitrário antes de exportar.
US-3: Como vendedor, quero exportar apenas os meus pedidos.
## Acceptance Criteria
AC-1: PDF gerado contém: cabeçalho com logo, período, total bruto,
total líquido (sem cancelados), tabela de pedidos.
AC-2: Pedidos cancelados aparecem na tabela com badge visual,
mas não compõem o total líquido.
AC-3: CPF do cliente renderizado no formato xxx.xxx.xxx-NN.
AC-4: Nome do arquivo: "vendas-{YYYY-MM-DD}-a-{YYYY-MM-DD}.pdf".
AC-5: Vendedor que tentar exportar período fora do próprio recebe 403.
## [NEEDS CLARIFICATION]
- NC-1: Acima de quantas linhas a geração vai pra fila?
- NC-2: PDF é cacheado? Por quanto tempo?
- NC-3: Email automático com link após geração assíncrona, ou só download?
Esses [NEEDS CLARIFICATION] são o ouro do SDD. O agente sinaliza o que ele não consegue inferir sozinho. Você responde, atualiza o spec, e segue. Sem isso, ele inventa default.
Passo 3: /plan — escolher stack e contratos
Agora sim entra a técnica. Comando:
/speckit.plan
Saída em .specify/specs/sales-report-pdf/plan.md:
# Plan: Exportação de relatório de vendas em PDF
## Stack
- Renderer: barryvdh/laravel-dompdf (suficiente pro layout simples).
- Action class: App\Actions\Reports\ExportSalesPdf.
- Controller: thin, só valida e chama a action.
- View: resources/views/reports/sales-pdf.blade.php.
- Fila: Laravel Queue. Threshold = 1.000 linhas.
## Contracts
- Endpoint: GET /reports/sales/export.pdf
- Query: ?from=YYYY-MM-DD&to=YYYY-MM-DD
- Resposta síncrona: application/pdf, 200.
- Resposta assíncrona: 202 + { job_id, poll_url }.
## Policies
- SalesReportPolicy::export — verifica role + scope de vendedor.
## Decisões registradas
- dompdf escolhido sobre Snappy: layout não usa CSS3 avançado.
- Cache: 5 minutos por (user_id + from + to).
Repare: o plan.md é o lugar onde decisão técnica vive versionada, não num PR comment que ninguém mais acha.
Passo 4: /tasks — quebrar em tarefas testáveis
/speckit.tasks
Saída em tasks.md:
# Tasks
T1. Criar migration de índice composto (user_id, status, created_at) em orders.
T2. Criar SalesReportPolicy com método export().
T3. Criar Action ExportSalesPdf com __invoke(User $user, CarbonPeriod $range).
T4. Criar SalesReportPdfRequest com validação de from/to.
T5. Criar SalesReportController@export, thin.
T6. Criar view Blade do PDF com mascaramento de CPF.
T7. Criar Job ExportSalesPdfJob para volumes acima do threshold.
T8. Pest: feature test para AC-1, AC-2, AC-3, AC-4, AC-5.
T9. Pest: unit test do mascaramento de CPF.
T10. Documentar no README a rota e os params.
Cada task é pequena, testável, e amarra a um AC. É aqui que o ganho aparece: em vez do agente cuspir 400 linhas de uma vez, ele entrega T1 → review → T2 → review.
Passo 5: /implement (com freio humano)
/speckit.implement T8
Antes de qualquer linha de aplicação, o agente escreve o teste Pest da T8. Pra valer a pena, peça que ele rode o teste antes de implementar — confirma que falha pelo motivo certo:
<?php
use App\Models\{User, Order};
it('exporta PDF com totais corretos ignorando cancelados (AC-2)', function () {
$admin = User::factory()->admin()->create();
Order::factory()->count(3)->for($admin)->create(['status' => 'paid', 'total' => 100]);
Order::factory()->for($admin)->create(['status' => 'canceled', 'total' => 999]);
$response = $this->actingAs($admin)
->get('/reports/sales/export.pdf?from=2026-04-01&to=2026-04-30');
$response->assertOk();
$response->assertHeader('content-type', 'application/pdf');
$pdfText = (new \Smalot\PdfParser\Parser())
->parseContent($response->getContent())->getText();
expect($pdfText)
->toContain('Total líquido: R$ 300,00')
->not->toContain('R$ 999,00');
});
Falhou? Bom. Agora o agente implementa a Action:
<?php
namespace App\Actions\Reports;
use App\Models\{Order, User};
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\CarbonPeriod;
final class ExportSalesPdf
{
public function __invoke(User $user, CarbonPeriod $range): string
{
$orders = Order::query()
->whereBetween('created_at', [$range->start, $range->end])
->when($user->isVendedor(), fn ($q) => $q->where('user_id', $user->id))
->orderBy('created_at')
->get();
$totalLiquido = $orders->reject->isCanceled()->sum('total');
return Pdf::loadView('reports.sales-pdf', [
'orders' => $orders,
'totalLiquido' => $totalLiquido,
'range' => $range,
])->output();
}
}
Roda o Pest de novo: passa. Próxima task. Esse é o loop. Cada task entra no commit com referência ao AC e ao spec — rastreabilidade que PR descrição em texto livre nunca entrega. Quer ver como esse ciclo se comporta numa feature de verdade, com o PR inteiro saindo do agente? A gente documentou a experiência em hands-on: um PR 100% gerado por agente em Laravel.
Limitações e pontos de atenção (a parte que ninguém posta no thread)
1. Markdown sprawl. Quatro arquivos por feature pequena. Em projeto com 200 features, vira inferno. Solução: SDD não é pra todo CRUD. Aplica em features com regra de negócio — relatório, integração, fluxo de aprovação. Migration de coluna nova não merece spec.md.
2. O agente diverge mesmo com spec rígida. Já vi spec dizendo "máscara CPF xxx.xxx.xxx-NN" e o código sair com "..***-NN". Não é falha do método, é a não-determinismo do LLM. O Pest é a rede de proteção — sem teste, SDD vira só burocracia.
3. Constituição é frágil. O Spec Kit propõe um constitution.md com regras imutáveis ("toda Action é final", "controller é thin"). Funciona em projeto novo. Em legado de 5 anos, a constituição só vale onde você forçar. Não dá pra retroagir — mas dá pra extrair uma spec reversa de um módulo existente e trazer o legado pro ciclo aos poucos; mostramos o caminho em do legado ao SDD: spec reversa em módulo legado.
4. LGPD e dados sensíveis. A spec precisa dizer explicitamente o que sai mascarado. Se você esquecer, o agente expõe. Trate isso como acceptance criteria de primeira classe, não como detalhe de implementação.
5. Não é silver bullet. Feature com 1 query SQL e zero regra de negócio é overhead. SDD ganha onde o custo do erro é alto: relatório financeiro, integração com gateway, fluxo de pagamento, exportação que vai pra contabilidade.
FAQ
Funciona sem o Spec Kit oficial?
Sim. O Spec Kit é só uma convenção de pastas + comandos /speckit.* no Claude Code. Você consegue o mesmo com .claude/commands/specify.md, plan.md e tasks.md custom. O claude-code-spec-workflow do Pimzino é uma alternativa pronta pra colar no projeto. E se você já roda SDD e quer comparar com outra metodologia estruturada, a gente destrinchou o BMAD Method pra quem já usa SDD. O importante é o ciclo, não o nome do comando.
Preciso refazer o projeto inteiro? Não. SDD é por feature. Legado segue como está. Toda feature nova entra pelo ciclo, e em 6 meses metade do código que importa já está versionada com spec.
Os testes Pest substituem o spec? Não. Spec é "o quê" e "por quê". Teste é "como verifico que funciona". Camadas diferentes. Quando alguém pergunta por que o pedido cancelado não soma, o spec responde. Quando alguém quer saber se ainda funciona depois do refactor, o teste responde.
dompdf aguenta relatório grande?
Pra PDF tabular simples, sim — até uns 2.000-3.000 pedidos render bem. Acima disso, ou se tiver gráfico/CSS3 avançado, troque por Browsershot (Headless Chrome). Mas isso é detalhe que mora no plan.md, não no spec.md.
Conclusão
Spec-Driven Development aplicado a Laravel não é mudança de framework, é mudança de input. O agente sai do modo adivinhador e entra no modo executor: a spec, com acceptance criteria explícitos, é o que ele lê pra gerar código. O markdown extra paga em rastreabilidade, em [NEEDS CLARIFICATION] que pega ambiguidade antes do PR, e em commits que amarram código a regra de negócio. O que sobra pro humano é o que sempre foi: pensar o problema, decidir trade-off, e revisar com critério.
O próximo passo depois de SDD é harness engineering — montar o ferramental ao redor do agente pra ele rodar em produto real, não em chatbot de demo (se o termo é novo pra você, começa por o que é um harness de IA). É exatamente o que vou destrinchar ao vivo no Do Prompt ao Harness, 11 e 12 de julho: dois dias construindo do zero um agent de vendas de verdade — Claude Code, Laravel, sem prompt mágico e sem chatbot de demo.
Sources:
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Conteúdo é o que não falta. Falta quem desembaralhe: o que importa agora é como implementar do jeito certo. No Clã você tem isso ao vivo, toda semana, com quem já filtrou o ruído.
Entrar no Clã