Documentação melhor que memória: escreva repositório para pessoas e agentes
1. Visão geral
Agente de programação está gradualmente se tornando um “membro” do time: ele lê código, corrige bugs, escreve testes e abre pull requests. O problema é que o agente não lembra nada entre as sessões. Existem ferramentas que oferecem recursos de “memória”, mas essas notas estão frequentemente vinculadas a uma pessoa específica, são difíceis de revisar e não seguem as versões do código.
Este artigo explica por que a documentação no repositório é mais confiável que a memória do agente. O artigo também guia você sobre como escrever um repositório Spring Boot de forma que pessoas novas no time e agentes façam o trabalho certo na primeira vez. Depois de ler, você saberá como escrever o arquivo de instruções AGENTS.md, consolidar todas as verificações em um único comando, transformar convenções em testes e documentar as decisões de design.
2. Por que a memória não é suficiente
Compare duas maneiras de um agente “saber” que a camada domain não deve depender do Spring:
- Memória: na última vez você mencionou, e ele salvou uma nota em algum lugar.
- Repositório: a regra está em
AGENTS.mde há um teste que falhará se alguém a violar.
A primeira abordagem tem várias fraquezas óbvias:
- As notas existem apenas na máquina ou conta de uma pessoa. Os colegas que usam um agente diferente não terão.
- Ninguém revisa essas notas. Se o agente “lembrar errado”, é muito difícil detectar.
- As notas não seguem branches. Quando a regra muda na
main, a memória antiga permanece.
A segunda abordagem é versionada e revisada via pull request, e corresponde a cada commit. Mais importante, também ajuda as pessoas. O que um agente precisa para fazer certo é quase idêntico ao que um desenvolvedor novo precisa na primeira semana.
Essa é a razão pela qual este tópico está recebendo atenção. O agente não expõe um novo problema. Ele expõe o custo da documentação ruim, porque o agente não pode perguntar “ei, como isso funciona?” como uma pessoa nova faria.
3. Arquivo de instruções para agentes: AGENTS.md
Muitas ferramentas de agentes leem um arquivo Markdown na raiz do repositório para obter instruções. O nome do arquivo depende da ferramenta, mas AGENTS.md é uma convenção comum. Se sua ferramenta usa um nome diferente, você pode criar um symlink em vez de duplicar o conteúdo.
3.1. O que deve conter
# AGENTS.md
## Projeto
Serviço de pedidos (Spring Boot 3, Java 21). Módulos principais: `order`, `payment`, `customer`.
## Comandos
- Build + todas as verificações: `./scripts/verify.sh`
- Executar um teste: `./mvnw -q test -Dtest=DiscountPolicyTest`
- Executar app localmente: `./mvnw spring-boot:run -Dspring-boot.run.profiles=local`
## Convenções obrigatórias
- `..domain..` não deve importar `org.springframework..` (há teste: `ArchitectureTest`).
- Moeda usa `BigDecimal`, nunca `double`.
- Toda mudança de comportamento de negócio deve ter teste em `src/test/java/.../domain`.
## Não fazer
- Não editar arquivos em `src/main/resources/db/migration` já merged; criar nova migration.
- Não fazer commit de `application-local.yml`.
## Leia mais
- Decisões de arquitetura: `docs/adr/`
- Fluxo de pagamento: `docs/payment-flow.md`
Cada seção tem um propósito:
- Comandos: agentes executam muitos comandos. Um comando errado causa muitas tentativas desperdiçadas. Escreva comandos precisos, prontos para copiar e colar.
- Convenções obrigatórias: cada linha deve indicar como verificar (nome do teste, regra de lint). Convenções sem verificação são fáceis de ignorar.
- Não fazer: liste ações com consequências difíceis de reverter.
- Leia mais: aponte para documentação detalhada em vez de duplicá-la aqui.
3.2. Mantenha conciso, aponte para a fonte da verdade
AGENTS.md deve ser conciso, compreensível em um minuto. O agente carrega este arquivo no contexto a cada sessão. Quanto maior o arquivo, mais contexto consome e mais fácil fica desatualizado.
A regra geral: não redescriva o que o código ou build já dizem. Não liste cada dependência, pois pom.xml já tem. Não explique cada endpoint, pois a especificação OpenAPI já tem. Apenas registre o que não pode ser deduzido do código.
Aviso: Nunca coloque segredos, tokens ou URLs internas sensíveis em
AGENTS.md. Esse arquivo é lido por ferramentas externas e fica no histórico do git para sempre.
4. Um único comando de verificação
Agentes trabalham melhor com um feedback claro: edite código, execute comando, veja pass ou fail. Se verificação completa requer quatro comandos diferentes em sequência, o agente pulará. As pessoas também.
#!/usr/bin/env bash
# scripts/verify.sh — executa exatamente como CI. Código de saída diferente de 0 significa não pronto para merge.
set -euo pipefail
echo ">> Compilar + testes unitários + testes de arquitetura"
./mvnw -B -q verify
echo ">> Verificar que nenhum TODO-AGENT foi deixado"
if grep -rn "TODO-AGENT" src/; then
echo "Ainda há TODO-AGENT não resolvido" >&2
exit 1
fi
echo "OK: pronto para abrir pull request"
Explicação:
set -euo pipefailinterrompe o script assim que um comando falha. Assim o agente recebe código de saída diferente de 0 em vez de um log longo com “OK” no final enganando.-B(modo batch) desabilita saída com cores e barra de progresso. Log mais limpo facilita o agente ler erros.- O passo
grepé um exemplo de convenção do time: o agente pode marcar incompletude comTODO-AGENT, mas não pode deixar sót ao abrir PR.
Quando executado com sucesso, você vê:
>> Compilar + testes unitários + testes de arquitetura
>> Verificar que nenhum TODO-AGENT foi deixado
OK: pronto para abrir pull request
O mais importante: CI deve usar exatamente este script. Se CI executar um conjunto de comandos diferente, verify.sh gradualmente divergirá e se tornará documentação falsa.
5. Transformar convenções em testes com ArchUnit
Uma frase como “domain não depende do Spring” na documentação é apenas um conselho. Transforme em teste, e vira lei. ArchUnit permite escrever regras de arquitetura como testes JUnit.
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
package com.nonla.shop;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
@AnalyzeClasses(packages = "com.nonla.shop", importOptions = ImportOption.DoNotIncludeTests.class)
class ArchitectureTest {
@ArchTest
static final ArchRule domainNaoDependeDaSpring = noClasses()
.that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("org.springframework..")
.because("domain deve ser testável sem Spring context, veja docs/adr/0003-domain-puro.md");
@ArchTest
static final ArchRule webNaoChamaDirectoRepository = noClasses()
.that().resideInAPackage("..web..")
.should().dependOnClassesThat().haveSimpleNameEndingWith("Repository")
.because("controller passa por service para manter transação em um lugar");
}
O mais valioso está em .because(...). Quando a regra é violada, a mensagem de erro contém o motivo e aponta para o ADR. Suponha que alguém adiciona @Component a DiscountPolicy no package domain, a saída será:
Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package
'..domain..' should depend on classes that reside in a package 'org.springframework..',
because domain deve ser testável sem Spring context, veja docs/adr/0003-domain-puro.md'
was violated (1 times):
Class <com.nonla.shop.order.domain.DiscountPolicy> is annotated with <org.springframework.stereotype.Component> ...
Um agente que lê essa mensagem sabe imediatamente o que fazer: remover a anotação, depois registrar o bean em uma classe @Configuration na camada infra. Uma boa mensagem de erro é documentação aparecer no momento certo.
6. Testes como documentação de negócio
Regras de negócio são o que agentes mais erroneamente interpretam. Ele não consegue adivinhar “desconto de primeira compra máximo de 50 mil”. Deixe o código e testes falarem.
package com.nonla.shop.order.domain;
import java.math.BigDecimal;
import java.math.RoundingMode;
public record Order(String customerId, BigDecimal subtotal, boolean firstOrder) {}
class DiscountPolicy {
static final BigDecimal FIRST_ORDER_RATE = new BigDecimal("0.10");
static final BigDecimal FIRST_ORDER_CAP = new BigDecimal("50000");
BigDecimal discountFor(Order order) {
if (!order.firstOrder()) {
return BigDecimal.ZERO;
}
BigDecimal raw = order.subtotal().multiply(FIRST_ORDER_RATE)
.setScale(0, RoundingMode.DOWN);
return raw.min(FIRST_ORDER_CAP);
}
}
Os testes devem nomear o comportamento, não o método:
package com.nonla.shop.order.domain;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Desconto de pedido")
class DiscountPolicyTest {
private final DiscountPolicy policy = new DiscountPolicy();
@Test
@DisplayName("Primeira compra recebe desconto de 10% no total")
void firstOrderGetsTenPercent() {
var order = new Order("c-1", new BigDecimal("200000"), true);
assertThat(policy.discountFor(order)).isEqualByComparingTo("20000");
}
@Test
@DisplayName("Desconto de primeira compra não excede 50 mil")
void firstOrderDiscountIsCapped() {
var order = new Order("c-1", new BigDecimal("2000000"), true);
assertThat(policy.discountFor(order)).isEqualByComparingTo("50000");
}
@Test
@DisplayName("A partir da segunda compra não há desconto")
void repeatOrderGetsNoDiscount() {
var order = new Order("c-1", new BigDecimal("2000000"), false);
assertThat(policy.discountFor(order)).isZero();
}
}
Explicação:
@DisplayNametransforma a saída do teste em uma especificação legível. Até produto consegue entender.isEqualByComparingTocomparaBigDecimalpor valor, então20000e20000.00são iguais. UsarisEqualToaqui é erro comum, pois compara escala também.- Se o agente recebe “aumente o desconto para 15%”, ele vê imediatamente qual teste mudar. Se ele acidentalmente omite o limite, o segundo teste falhará.
Ao executar, o relatório fica assim:
Desconto de pedido
✔ Primeira compra recebe desconto de 10% no total
✔ Desconto de primeira compra não excede 50 mil
✔ A partir da segunda compra não há desconto
7. Registre o “por quê”: ADR e package-info
Código diz o que o sistema faz. Teste diz o que deve fazer. Mas nada explica por quê escolheu esse caminho. Sem o “por quê”, agente ou “limpa” o que parece redundante mas tem motivo.
Architecture Decision Record (ADR) é um arquivo curto para cada decisão:
# 0003. Camada domain não depende do Spring
Status: Aceito
## Contexto
Testes da domain precisavam iniciar Spring context, lento e frágil.
## Decisão
Package `..domain..` usa apenas JDK. Beans são declarados em `..infra.config`.
## Consequências
- Testes da domain usam JUnit puro.
- Precisa escrever classe de configuração. Verificado por `ArchitectureTest`.
Para notas coladas a um package específico, package-info.java é o lugar certo, pois agente abrindo a pasta vê imediatamente:
/**
* Regras de negócio de pedido. Usa apenas JDK, sem Spring (ADR 0003).
* Todo valor monetário é BigDecimal, unidade VND, arredonda para baixo em centavos.
*/
package com.nonla.shop.order.domain;
8. Erros comuns e quando não fazer
Documentação duplicada. A mesma regra aparece em README.md, AGENTS.md e wiki. Depois de meses, três lugares dizem três coisas diferentes. Escolha uma fonte de verdade e aponte para ela de outros lugares.
Instruções contradizem CI. AGENTS.md diz “execute mvn test”, mas CI também roda teste de integração. Agente relata “pronto” enquanto PR fica vermelho.
Escrever para agente com tom vago. Frase “escreva código limpo” não ajuda. Frase “método público em ..service.. deve ter teste” ajuda, melhor ainda se verificável.
Colocar tudo em um arquivo. AGENTS.md com milhares de linhas dilui contexto. Informação crítica desaparece.
Aviso: Nem todo repositório precisa deste kit completo. Um script de uso único, um protótipo a descartar, ou repositório com poucas centenas de linhas de código precisam apenas
README.mdcom comando. ArchUnit faz sentido quando você tem várias camadas ou módulos com limites reais. Escrever documentação para o que não existe cria mais carga técnica sem valor.
Ao contrário, memória de agente ainda tem valor. Serve para preferências pessoais como tom ao explicar ou editor usado. Essas coisas não pertencem ao repositório. Mas regras do projeto sempre devem estar no repositório.
9. Conclusão
Agente não precisa de memória melhor, precisa de repositório mais claro. Você viu como:
- Escrever
AGENTS.mdconciso, com comandos precisos e convenções verificáveis. - Consolidar toda verificação em
scripts/verify.she usar o mesmo script em CI. - Transformar convenções arquiteturais em testes ArchUnit, com mensagens de erro apontando para o motivo.
- Usar testes com
@DisplayNamecomo especificação de negócio. - Registrar o “por quê” com ADR e
package-info.java.
Tudo isso ajuda pessoas primeiro. O agente é apenas a razão de não poder mais adiar. Comece pequeno: escreva verify.sh e as primeiras linhas de AGENTS.md esta semana.