~ / tutoriais /versionando-specs-documentacao-viva-sem-readme-abandonado $ _

Versionando specs: como manter documentação viva sem virar mais um README abandonado

Lucas Souza Lucas Souza 5 min de leitura Tutoriais
Versionando specs: como manter documentação viva sem virar mais um README abandonado

Todo time pequeno tem aquele README que começou bonito e morreu na segunda sprint. Alguém criou, alguém atualizou duas vezes, e depois o código seguiu sozinho. Quando entra dev novo, a doc serve mais para confundir do que para ajudar.

O problema raramente é falta de boa vontade. É processo. Documentação que vive solta, fora do fluxo de Git, fora de revisão de PR, fora de qualquer validação automática, vira artefato histórico em semanas.

Neste post vamos montar um tripé concreto — Git + spec versionada + ADR (Architecture Decision Records) — protegido por hooks de pré-commit que travam drift. É o esquema mínimo que dá para um time de 3 a 8 devs sem precisar contratar "engenheiro de documentação".

TL;DR

  • O que é: documentação viva versionada junto do código, com spec descrevendo o "o quê" e ADR registrando o "porquê".
  • Stack: Git, Markdown, MADR ou adr-tools, pre-commit, markdownlint-cli2.
  • Custo: zero. Tudo open source, roda local e em CI.
  • Repositório de referência: github/spec-kit (toolkit oficial do GitHub para spec-driven development).

Por que documentação morre — e por que isso volta a importar agora

Em 2011, Michael Nygard escreveu o post fundador de ADR com uma frase que segue valendo:

"Large documents are never kept up to date. Small, modular documents have at least a chance at being updated."

A tese é boba de tão óbvia. Documento grande não é atualizado. Documento pequeno, modular, próximo do código, tem chance. ADRs nasceram dessa premissa: registrar uma decisão por arquivo, em Markdown, dentro do repo, do tamanho de um e-mail.

A ThoughtWorks colocou ADR em Adopt no Tech Radar sete anos depois do post original. Não foi modinha — foi reconhecimento de que a única doc que sobrevive é a que mora junto do código.

Em 2026, o assunto voltou ao radar por outro motivo: agentes de IA precisam de contexto durável. Quando você usa Claude Code, Copilot, Cursor, qualquer harness de agente, a qualidade do output depende do quanto o agente consegue entender a intenção do projeto. Código bruto não conta a história. Spec clara e ADR atualizado contam.

O GitHub Spec Kit, lançado pelo próprio GitHub, formaliza isso. A ideia central do projeto é direta: intent is the source of truth. A spec deixa de ser comentário decorativo e vira o contrato que o agente lê para gerar código.

Ou seja: doc abandonada não é só problema humano. É problema de produtividade do harness inteiro.

O tripé: Git + spec + ADR

Cada vértice resolve uma coisa diferente.

Git é o histórico. Toda mudança em doc passa por PR, revisão, blame. Quem mudou, quando, por quê fica registrado pelo próprio versionamento — não tem reunião que substitua git log.

Spec é o "o quê". Descreve a feature, o endpoint, o módulo. Mora em specs/ ao lado do código. Tem formato fixo: contexto, requisitos funcionais, contrato (entrada/saída), critérios de aceite. Curta. Uma página é mais que suficiente. Spec gigante é README disfarçado.

ADR é o "por quê". Cada decisão arquitetural significativa vira um arquivo em docs/adr/NNNN-titulo.md. Status (proposed, accepted, deprecated, superseded), contexto, decisão, consequências. ADR aceito é imutável — se mudar de ideia, escreve-se um novo ADR que supersede o antigo. Isso é o que dá audit trail real.

A combinação importa. Spec sozinha vira documentação de produto desconectada da implementação. ADR sozinho vira filosofia. Git sozinho é só histórico de diff. Os três juntos é que reproduzem como o time pensa.

Mão na massa — estrutura mínima

Estrutura de pastas que cabe em qualquer projeto:

.
├── docs/
   └── adr/
       ├── 0001-usar-laravel-12.md
       ├── 0002-postgres-em-vez-de-mysql.md
       └── 0003-sqs-para-fila-de-eventos.md
├── specs/
   ├── billing-recurring-charge.md
   └── webhook-stripe.md
└── src/
    └── ...

Para ADR, dois caminhos práticos. Bash puro com adr-tools:

brew install adr-tools
adr init docs/adr
adr new "Usar Postgres em vez de MySQL"

Ou via Node, com log4brains, que ainda gera site estático navegável:

npx -y log4brains@latest init
npx -y log4brains@latest adr new
npx -y log4brains@latest preview

Modelo de ADR no formato MADR (versão minimal, suficiente para 90% dos casos):

# 3. Usar SQS para fila de eventos

Date: 2026-05-06
Status: accepted

## Context

Precisamos desacoplar processamento de webhooks externos do
ciclo de request HTTP. Volume estimado: 50k eventos/dia,
com picos de 5x em horário comercial. Time tem operação AWS,
mas zero experiência com Kafka.

## Decision

Usar AWS SQS Standard Queue + Lambda consumer. Dead-letter
queue após 3 tentativas. Retenção de 14 dias.

## Consequences

- Custo previsível (~US$ 8/mês no volume estimado).
- Latência aceitável (segundos), incompatível com casos
  que exijam processamento em <100ms.
- Reprocessamento manual via console quando DLQ encher.
- Substitui qualquer discussão futura sobre Kafka até que
  tenhamos volume real >500k/dia.

Cinco minutos para escrever. Salva semanas de "por que a gente escolheu isso mesmo?" daqui a um ano.

A spec da feature é igualmente curta. Exemplo de specs/webhook-stripe.md:

# Webhook Stripe — eventos de assinatura

## Contexto
Recebemos eventos de billing recorrente do Stripe.
Precisamos atualizar status local e emitir notificação
ao usuário em <30s do evento.

## Contrato
- POST /webhooks/stripe
- Header obrigatório: Stripe-Signature
- Body: payload assinado conforme docs do Stripe
- Resposta: 200 vazio em sucesso, 400 em assinatura inválida

## Eventos suportados
- customer.subscription.created
- customer.subscription.updated
- customer.subscription.deleted
- invoice.payment_failed

## Critérios de aceite
- Assinatura inválida não chega ao processamento.
- Evento duplicado (mesmo Stripe-Event-Id) é idempotente.
- Falha de processamento volta para fila SQS (ver ADR 0003).
- Notificação ao usuário em <30s p95.

Repare como a spec referencia o ADR. Esse linking é o que mantém a estrutura coerente sem virar um wiki gigante.

Hooks de pré-commit que travam o drift

Toda essa estrutura vira teatro se ninguém for obrigado a manter. É aí que entram os hooks. Não é um nice-to-have — é o mecanismo que transforma "boa intenção" em "vai falhar o commit se você relaxar".

Configuração base do pre-commit framework em .pre-commit-config.yaml:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-merge-conflict

  - repo: https://github.com/DavidAnson/markdownlint-cli2
    rev: v0.13.0
    hooks:
      - id: markdownlint-cli2
        files: \.(md|markdown)$

  - repo: https://github.com/lycheeverse/lychee
    rev: v0.15.1
    hooks:
      - id: lychee
        args: ["--no-progress", "--exclude-mail", "specs/", "docs/"]

  - repo: local
    hooks:
      - id: adr-immutable
        name: ADRs aceitos não podem ser editados
        entry: scripts/check-adr-immutable.sh
        language: script
        files: ^docs/adr/.*\.md$

      - id: spec-or-adr-required
        name: Mudança em src/ exige spec ou ADR atualizado
        entry: scripts/check-spec-or-adr.sh
        language: script
        stages: [commit]

Os três primeiros blocos são padrão. O markdownlint-cli2 impede markdown quebrado. O lychee valida links — porque ADR com link morto é pior que ADR ausente: dá ilusão de doc.

Os dois hooks locais são onde mora o controle real.

scripts/check-adr-immutable.sh valida que arquivos de ADR com Status: accepted não foram modificados:

#!/usr/bin/env bash
set -euo pipefail

for file in "$@"; do
  if git diff --cached --name-only | grep -q "^${file}$"; then
    base=$(git show HEAD:"$file" 2>/dev/null || echo "")
    if echo "$base" | grep -qi "^Status:.*accepted"; then
      if ! git diff --cached "$file" | grep -q "^+Status:.*superseded"; then
        echo "ERRO: $file está accepted. Crie um ADR novo que supersede."
        exit 1
      fi
    fi
  fi
done

scripts/check-spec-or-adr.sh força que mudanças em src/ venham com atualização correspondente:

#!/usr/bin/env bash
set -euo pipefail

src_changed=$(git diff --cached --name-only | grep -E '^src/' || true)
docs_changed=$(git diff --cached --name-only | grep -E '^(specs|docs/adr)/' || true)

if [[ -n "$src_changed" && -z "$docs_changed" ]]; then
  echo "AVISO: mudança em src/ sem atualizar spec ou ADR."
  echo "Se for refactor puro, commit com: git commit --no-verify"
  echo "Caso contrário, atualize a spec ou registre um ADR."
  exit 1
fi

A regra é deliberadamente chata. O --no-verify é a válvula de escape — refactor puro não exige doc nova. Mas o default é fricção: o dev tem que parar e pensar se aquela mudança merece um registro. Em 80% dos casos, merece.

Esses scripts cabem em 30 linhas cada. Não é magia. É processo automatizado o suficiente para sobreviver ao primeiro deadline apertado.

Limitações e onde isso quebra

Hook lento mata produtividade. Se o pre-commit demora mais de 3 segundos, o time começa a usar --no-verify por reflexo. Cuide para que cada hook seja sub-segundo no caminho feliz. Linter de markdown e checagem de path são rápidos. Validação de link via HTTP é lenta — rode em CI, não em pré-commit.

ADR não substitui design doc. Decisão grande de arquitetura — migração de monólito, escolha de banco principal, refatoração de camada inteira — pede um RFC ou design doc dedicado, com diagramas, alternativas comparadas, simulações. ADR é o resumo executivo dessa decisão depois que ela foi tomada. Confundir os dois é como tentar projetar uma casa via post-it.

Spec executável (BDD, contract testing) é o estágio seguinte e tem custo real. Se o time ainda não tem cultura de teste automatizado consistente, comece pelo tripé estático deste post. Spec executável vem depois, quando a base já está sólida — caso contrário, vira mais um framework abandonado.

E o de sempre: ferramenta não substitui hábito. Se o time não revisa ADR no PR review, a estrutura morre na primeira semana.

FAQ rápido

Funciona com qualquer linguagem? Sim. Tudo é Markdown e Git. Os exemplos aqui usam shell e Node, mas a estrutura é agnóstica. Em projetos PHP/Laravel a única adaptação é integrar o pre-commit ao Husky ou rodar via Composer script — funciona igual.

Quando vale criar ADR? Toda decisão que, daqui a um ano, alguém vai questionar "por que escolhemos X em vez de Y?". Escolha de banco, fila, autenticação, padrão de evento, política de retry. Não é para registrar nome de variável.

Como integrar com agente de IA? Aponte o agente para specs/ e docs/adr/ como contexto durável. No Claude Code, isso entra no CLAUDE.md ou em referências do projeto. O agente passa a entender intenção e restrições do time, não só padrões genéricos do treinamento. Esse é justamente o tipo de scaffolding que vamos botar de pé no Harness Engineering com Claude Code, workshop ao vivo nos dias 16 e 17 de maio em que a galera constrói um app real — recebe link de produto, pesquisa alternativas em e-commerces, devolve recomendação estruturada — usando Claude Code com Laravel e NativePHP.

E se o time simplesmente não escrever? Aí o problema não é doc, é alinhamento. Comece pequeno: um ADR por sprint, um hook só (markdownlint), e revisão obrigatória no PR. Quando o primeiro novato disser "encontrei a resposta no ADR 0007", o ciclo se prova sozinho.

Fechando

Documentação viva não é um produto. É um efeito colateral de processo bem montado. Git mantém o histórico. Spec versionada mantém o "o quê" próximo do código. ADR mantém o "porquê" auditável. Hooks de pré-commit transformam "boa prática" em "regra do repositório".

Nada disso é novo. Nygard descreveu ADR em 2011. Docs-as-code virou termo comum em 2015. O que mudou em 2026 é que harness de IA precisa desse scaffolding para funcionar bem — agente sem contexto durável é dev novo todo dia, sem onboarding. O time que já tem spec e ADR atualizados ganha, de graça, um agente que entende o produto.

Se você ainda não tem nada disso no repo, começa hoje. Cria a pasta docs/adr/, escreve o primeiro ADR sobre uma decisão recente, configura markdownlint no pre-commit. Em uma tarde está rodando. Daqui a três meses, o README abandonado vira artefato arqueológico — e a doc viva ocupa o lugar que ele nunca soube preencher.

Lucas Souza
Lucas Souza

{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.

Você também pode gostar

5 sinais de que sua especificação virou burocracia (e como voltar à base bem feita)
Tutoriais

5 sinais de que sua especificação virou burocracia (e como voltar à base bem feita)

Spec-driven virou padrão em 2026, e com ele veio o risco do pêndulo: spec gigante, aprovada em comitê, ignorada pelo time e filtrada pelo agente. Cinco sintomas concretos e o ajuste prático para cada um.

· 7 min
Do legado ao SDD: refatorando um módulo bagunçado a partir de uma specification reversa
Tutoriais

Do legado ao SDD: refatorando um módulo bagunçado a partir de uma specification reversa

SDD nasceu pensando em greenfield. A maioria dos tutoriais começa em mkdir projeto-novo e ignora quem está em projeto maduro. Reverse-spec resolve isso: o agente lê o código existente, gera a specification, humano revisa, e a partir daí o ciclo SDD clássico roda. Vou mostrar 4 passos práticos pra aplicar a técnica num módulo legado real, sem reescrever do zero e sem precisar esperar comando oficial em ferramenta nenhuma.

· 10 min
SDD do zero em Laravel: transformando uma feature real em specification executável
Tutoriais

SDD do zero em Laravel: transformando uma feature real em specification executável

Vibe coding com agente em Laravel funciona até a feature ter regra de negócio. Aí o agente inventa. Spec-Driven Development resolve isso virando a especificação na fonte da verdade. Neste post a gente percorre o ciclo PRD, spec, plan, tasks, código e testes em uma feature aparentemente boba: exportar relatório de vendas em PDF. Stack PHP, Claude Code e Spec Kit, do zero.

· 8 min
O paradoxo da especificação: quando SDD vira overengineering disfarçado de boa prática
Tutoriais

O paradoxo da especificação: quando SDD vira overengineering disfarçado de boa prática

Quatro horas escrevendo spec para uma feature de duas horas é o sintoma. SDD virou ortodoxia em 2026 e pouca gente discute o custo: tempo de leitura, revisão dupla, drift entre spec e código, falsa sensação de controle. Aqui vamos ver de onde veio o método, onde entrega de verdade, onde virou cerimônia, e como aplicar spec proporcional ao risco.

· 9 min

VirguIA

beer & code assistant

conectando…

Não foi possível iniciar o chat agora.

tocando