Especificação Como Artefato Vivo: Implementando Spec-Driven Development em Projetos que Já Existem
Você já recebeu um código gerado por IA que compilava, passava nos testes que existiam e ainda assim estava completamente errado em relação ao que o time realmente precisava? O agente entendeu “cadastrar usuário” como uma operação simples de insert, enquanto a regra de negócio exigia validação de duplicidade por CPF, envio assíncrono de e-mail de confirmação e um rollback específico se a integração com o sistema de billing falhasse. Nenhuma dessas regras estava em lugar nenhum além da cabeça de quem especificou a tarefa verbalmente numa call de quinze minutos.
Esse é o sintoma mais comum de projetos que adotaram IA generativa no fluxo de desenvolvimento sem resolver o problema real: a especificação nunca foi um artefato formal, sempre foi um entendimento tácito distribuído entre product owners, desenvolvedores seniores e algum documento desatualizado no Confluence. Quando o código era escrito só por humanos, esse entendimento tácito se propagava por osmose — reuniões, code review, “pergunta pro fulano”. Quando parte do código passa a ser gerado por um agente que não participou de nenhuma dessas conversas, a lacuna fica visível e cara.
Neste artigo o foco não é explicar o que é Spec-Driven Development em tese, mas mostrar como estruturar esse fluxo em um projeto que já está rodando, com código legado, débito técnico e pressão de prazo — os ingredientes reais do dia a dia, não o cenário ideal de greenfield.
Por que specs informais quebram quando a IA entra no fluxo
Segundo o guia da Augment Code sobre Spec-Driven Development, a diferença central entre esse modelo e o desenvolvimento tradicional é que a especificação deixa de ser um documento de referência e passa a ser a fonte de verdade executável — o código é tratado como artefato derivado, não como ponto de partida.
Isso muda a mecânica do trabalho de forma concreta. Em vez de abrir uma branch e começar a escrever a função, você escreve primeiro um arquivo de especificação que descreve comportamento, contratos de entrada e saída, casos de borda e critérios de aceite. Só depois esse arquivo alimenta o agente — Claude Code, Cursor, ou qualquer ferramenta equivalente — que gera a implementação.
A IBM, em artigo da pesquisadora Anna Gutowska sobre o tema, reforça um ponto que costuma ser subestimado: a especificação formal reduz a ambiguidade que os modelos de linguagem exploram para “preencher lacunas” com suposições plausíveis, mas erradas. Um LLM não erra por falta de capacidade técnica na maioria dos casos — erra porque interpreta razoavelmente bem um enunciado mal definido, e a interpretação razoável nem sempre é a correta para o seu domínio de negócio.
💡 Dica do Mestre: Antes de adotar SDD formalmente, vale assistir à apresentação Spec-Driven Development (SDD) Engenharia de Software Agêntica, que detalha como equipes estruturam o ciclo de vida da especificação em projetos com múltiplos agentes trabalhando em paralelo.
Estruturando o arquivo de especificação
Na prática, uma spec não é um PRD genérico de produto. Ela precisa ser estruturada o suficiente para que um agente — ou um humano revisando o output do agente — consiga verificar objetivamente se a implementação atende ou não aos critérios. Um formato que funciona bem combina contexto, contrato e casos de teste no mesmo arquivo.
Veja um exemplo de spec para uma funcionalidade de cancelamento de assinatura em um sistema de billing, escrito em Markdown estruturado — formato que a maioria dos agentes de código consegue parsear sem ambiguidade:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
## Spec: Cancelamento de Assinatura ### Contexto Usuário autenticado solicita cancelamento da assinatura ativa. Sistema deve processar o cancelamento sem interromper o acesso até o fim do ciclo de cobrança já pago. ### Contrato da função ``` cancelSubscription(userId: string, reason: CancelReason): Promise ``` ### Regras de negócio 1. Se o usuário não tiver assinatura ativa, retornar erro `NO_ACTIVE_SUBSCRIPTION` 2. O acesso permanece válido até `currentPeriodEnd`, não é revogado na hora 3. Deve registrar `reason` em log de auditoria com timestamp e userId 4. Deve disparar evento `subscription.cancelled` na fila (não bloquear a resposta) 5. Se a chamada à fila falhar, a operação de cancelamento NÃO deve ser revertida — logar erro e seguir (consistência eventual) ### Casos de teste obrigatórios - Cancelamento de assinatura ativa retorna sucesso e mantém acesso até o fim do período - Cancelamento de usuário sem assinatura retorna NO_ACTIVE_SUBSCRIPTION - Falha no envio do evento não impede o cancelamento de ser persistido - reason inválido (fora do enum CancelReason) retorna erro de validação antes de tocar no banco ### Fora de escopo - Reembolso proporcional (feature separada, ver spec billing-refund.md) - Reativação de assinatura cancelada |
Note que essa spec não descreve implementação — não diz se vai usar um ORM específico ou qual biblioteca de fila. Ela descreve comportamento observável e restrições. Isso é deliberado: a spec precisa sobreviver a uma troca de stack sem precisar ser reescrita.
O agente como executor, o humano como revisor de contrato
Com a spec pronta, o fluxo muda de “peça para o agente implementar a feature X” para “peça para o agente implementar exatamente o contrato descrito neste arquivo”. A diferença na prática é grande. Um exemplo de instrução para um agente como Claude Code, referenciando o arquivo de spec diretamente:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Implemente a função cancelSubscription seguindo exatamente o contrato em specs/cancel-subscription.md. Use o repositório SubscriptionRepository já existente em src/billing/subscription.repository.ts para persistência. Gere também os testes descritos na seção "Casos de teste obrigatórios" usando o framework de testes do projeto (Vitest). Não implemente nada listado em "Fora de escopo". |
O retorno do agente, nesse modelo, passa a ser avaliável contra um checklist objetivo: cada caso de teste da spec foi coberto? O contrato de assinatura de função bate? As regras de negócio numeradas estão todas refletidas em algum ponto do código ou dos testes? Isso transforma a revisão de código gerado por IA de um exercício de “isso parece certo” para um exercício de verificação de conformidade, que é mais rápido e muito menos sujeito a viés de confirmação.
Um exemplo em TypeScript do que esse agente provavelmente geraria, já alinhado ao contrato:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
export async function cancelSubscription( userId: string, reason: CancelReason ): Promise { const subscription = await subscriptionRepository.findActiveByUserId(userId); if (!subscription) { return { success: false, error: 'NO_ACTIVE_SUBSCRIPTION' }; } if (!Object.values(CancelReason).includes(reason)) { return { success: false, error: 'INVALID_REASON' }; } await subscriptionRepository.markAsCancelled(subscription.id, reason); await auditLog.record({ userId, action: 'subscription_cancelled', reason, timestamp: new Date(), }); try { await eventQueue.publish('subscription.cancelled', { userId, subscriptionId: subscription.id, }); } catch (err) { logger.error('Falha ao publicar evento de cancelamento', err); // intencional: não propaga o erro, cancelamento já foi persistido } return { success: true, accessUntil: subscription.currentPeriodEnd, }; } |
O detalhe do try/catch silencioso em torno da publicação do evento não é acidente nem gambiarra do agente — é a regra 5 da spec sendo cumprida à risca. Sem a spec explícita, essa decisão de design ficaria a critério da interpretação do modelo, e boa parte dos agentes, por padrão, propagaria o erro e falharia a operação inteira, o que violaria a regra de negócio real.
Retrofitting: aplicando SDD em código legado
A parte mais difícil não é adotar SDD em uma feature nova — é introduzi-lo em um módulo que já existe, sem spec nenhuma, com anos de decisões implícitas acumuladas. O caminho mais sustentável aqui é inverso ao fluxo padrão: em vez de escrever a spec e depois o código, você usa o agente para extrair uma spec candidata a partir do código existente, e então a equipe valida e corrige essa spec antes de tratá-la como fonte de verdade.
|
1 2 3 4 5 6 7 8 9 10 |
Analise o arquivo src/billing/subscription.service.ts e gere uma especificação no formato specs/*.md (contexto, contrato, regras de negócio numeradas, casos de teste observados). Liste separadamente qualquer comportamento que pareça ser bug não intencional, marcando como "COMPORTAMENTO SUSPEITO — validar com o time" em vez de documentar como regra. |
Essa instrução final é crítica. Código legado frequentemente tem comportamento que nunca foi intencional — um null check que mascarava um bug de outra camada, uma ordem de operações que só funciona por acidente de timing. Se o agente documentar esses comportamentos como regra de negócio oficial, a spec vira um contrato que perpetua o bug para sempre. Separar explicitamente “comportamento documentado” de “comportamento suspeito” evita esse tipo de fossilização de defeito.
Segundo a discussão na comunidade r/vibecoding sobre escalabilidade de SDD, esse é exatamente o ponto em que times que tentam adotar o modelo de forma apressada tropeçam: tratam a primeira versão da spec gerada automaticamente como definitiva, sem o ciclo de revisão humana, e acabam formalizando dívida técnica em vez de pagá-la.
Validação contínua: a spec não pode ficar desatualizada
O risco mais sério de longo prazo em SDD é o mesmo risco que qualquer documentação sempre teve: divergir da realidade do código com o tempo. A diferença é que, nesse modelo, a spec não é só documentação — é o contrato que o agente vai usar na próxima alteração. Se ela estiver desatualizada, o agente vai gerar código consistente com uma regra que não existe mais.
A prática que resolve isso na maioria dos times que adotaram o fluxo de forma madura, de acordo com a análise publicada no artigo sobre ferramentas e adoção real de SDD em 2026, é tratar a spec como parte do pull request, não como documento à parte. Toda alteração de comportamento exige alteração correspondente na spec, no mesmo commit, revisada pelo mesmo reviewer. Isso pode ser reforçado via CI:
|
1 2 3 4 5 6 7 8 9 10 |
# Exemplo de verificação simples em pipeline (pseudocódigo shell) if git diff --name-only origin/main | grep -q "src/billing/"; then if ! git diff --name-only origin/main | grep -q "specs/.*cancel-subscription.*\.md"; then echo "ERRO: alteração em src/billing/ sem atualização de spec correspondente" exit 1 fi fi |
Esse tipo de verificação é propositalmente simples e não cobre todos os casos, mas cumpre o papel de lembrete estrutural: ninguém altera o contrato de negócio sem tocar no arquivo que o descreve. Times maiores evoluem isso para ferramentas mais sofisticadas de rastreamento, mas o princípio é o mesmo em qualquer escala.
Quando SDD não compensa o esforço
Vale ser honesto sobre o custo. Escrever specs estruturadas para cada mudança pequena é overhead desnecessário em protótipos, scripts internos de uso único ou código com vida útil curta. SDD compensa quando o código vai ser mantido por múltiplas pessoas, por muito tempo, e quando os agentes de IA vão continuar operando sobre ele repetidamente — é um investimento que se paga em manutenção futura, não em velocidade imediata de entrega. Aplicar o modelo a um script de migração que roda uma vez só é desperdício de disciplina.
Leve essa discussão para quem já está testando na prática
Spec-Driven Development ainda está em formação como prática de mercado — as ferramentas, os formatos de arquivo e as convenções de integração com agentes mudam rápido, e trocar experiência com quem está aplicando isso em projetos reais, com as mesmas dores de legado e prazo, vale mais do que qualquer guia teórico. Se você quer discutir implementações concretas, trocar templates de spec e ver o que tem funcionado (ou não) em times diferentes, entre na Comunidade Dev’s AI.
Conclusão
Spec-Driven Development não resolve o problema de agentes de IA gerarem código errado por falta de inteligência — resolve o problema, muito mais comum, de agentes gerarem código tecnicamente correto para uma interpretação diferente da que o time pretendia. A especificação formal, tratada como artefato vivo e versionado junto do código, fecha essa lacuna de forma verificável, em vez de depender de prompts cada vez mais longos tentando compensar contexto que nunca foi escrito em lugar nenhum. O esforço inicial de estruturar specs é real, mas ele se compara ao mesmo esforço que qualquer engenharia séria já fazia com contratos de API bem definidos — só que agora esse contrato também orienta quem, ou o que, escreve o código.