Lei sobre o número: quem o decide, com que régua, e o que ele promete a quem já instalou. Complementa
packaging.md, que trata de como o artefato chega ao disco do cliente — aqui trata-se de o que o número diz sobre o que chegou. Amarrada ao Definition of Done (CLAUDE.md).
| Se você quer… | Vá para |
|---|---|
| decidir se sua mudança é patch, minor ou major | §A régua |
| saber por que não é você quem escolhe o número | §Ninguém escolhe o número |
| escrever o fragmento que o seu PR precisa trazer | §O fragmento |
| saber qual versão está publicada agora | §A versão em vigor |
A pergunta que decide o número não é "o que mudou no código?". Tudo muda — essa pergunta não separa nada, e uma régua que não separa nada devolve sempre a mesma resposta.
A pergunta é:
"O que o dono da VPS precisa fazer?"
Porque o número não é um rótulo de esforço nem um troféu de tamanho: é uma promessa
operacional para alguém que vai rodar bash update.sh sem ler o diff, num servidor onde o
WhatsApp da empresa dele está atendendo cliente agora.
Quem lê o número é o operador, não o autor. A régua tem que ser escrita da cadeira dele.
| O que acontece com quem já roda | Número |
|---|---|
| Não precisa fazer nada, e nada que funcionava mudou de forma | patch — 1.6.0 → 1.6.1 |
| Não precisa fazer nada, mas ganhou capacidade nova | minor — 1.6.1 → 1.7.0 |
Precisa agir: editar .env, rodar comando, ou algo que existia sumiu / mudou de forma |
major — 1.7.0 → 2.0.0 |
A linha de baixo não é escolha nossa: ela já é lei em CLAUDE.md, na doutrina de packaging
e no ADR 0001 — bump que exige edição manual não
entra; vira issue com plano de migração e vai para uma major. Esta doutrina apenas estende
a mesma lógica para baixo, para a faixa onde todas as releases do projeto realmente caem.
Esta é a régua que estava em vigor, e ela é a causa mecânica do problema que originou este
documento. Escrita em voz alta no commit de release da v1.6.0 (e9df4bae):
"MINOR porque muda comportamento visível ao cliente final."
Um conserto de bug, por definição, muda comportamento visível ao cliente final. Sob essa
régua todo conserto é minor, e o número perde a capacidade de distinguir "consertamos o que
estava quebrado" de "o sistema faz algo novo". Medido no histórico: seis minors e duas
patches em trinta dias, com v1.4.0 → v1.6.0 em três dias.
O caso da v1.6.0 é o mais instrutivo, porque o número estava certo — aquela versão trazia os formulários do Respondi, capacidade nova de verdade. Ele estava certo pelo motivo errado, justificado pelo conserto e não pela adição. Régua que acerta por acidente erra na próxima.
Antes de escolher, escreva a frase que o operador vai ler na tela de atualização:
- "Você não precisa fazer nada." → patch
- "Você não precisa fazer nada; agora o sistema também faz X." → minor
- "Antes de atualizar, você precisa…" → major
Se a terceira frase é verdadeira, o número é major mesmo que a mudança seja pequena — o custo que ele mede é o do operador, não o do autor.
O número é calculado a partir do que os PRs declararam, nunca digitado por quem está cortando a release. Isso não é preferência de estilo: é o que torna impossível a colisão entre duas sessões de trabalho paralelas.
Enquanto a escolha era humana, ela dependia de ler git tag e somar um — e duas sessões que
leem a mesma lista no mesmo dia chegam ao mesmo número. O resultado medido está no commit
ac9472c5, escrito pela sessão que descobriu o estrago por acaso:
"A 1.4.1 está no CHANGELOG e NÃO tem tag."
Uma sessão escreveu a seção da versão e nunca criou a tag. Como a tela de atualização mostra a seção da versão-alvo, os dois avisos da 1.4.1 — um deles instruindo o operador a apagar a conexão que estava funcionando — teriam desaparecido para quem pulasse direto para a 1.5.0.
Com o número derivado, a pergunta "qual é a próxima versão?" deixa de ter dono e passa a ter resposta: é função do conjunto de fragmentos acumulados, e duas sessões que rodem o cálculo obtêm o mesmo resultado porque olham para o mesmo conjunto.
git tag na máquina de alguém é o ponto onde o número deixa de ser revisável. A tag é criada
pelo CI, a partir de um PR de release, e só de um commit contido na main.
Três razões medidas, todas com consequência no parque instalado:
- A tag é o gatilho de atualização de todo mundo.
hostgator-setup-kit/agent.shfazgit fetch --tagsehostgator-setup-kit/update.shpuxa a imagem por número. Tag errada não é erro cosmético de changelog: é o seletor do que cada VPS baixa. - Uma tag
v*de qualquer branch move o canalstable. O workflow de publicação não testa se o commit está namain. - Mover uma tag já publicada quebra a VPS e mente sobre o motivo. O
git fetch --tagsdoupdate.shrecusa a tag movida (would clobber existing tag) e sai com erro — que o script relata como "não consegui falar com o GitHub". O operador fica no código antigo procurando um problema de rede que não existe.
Daí a regra que a doutrina de packaging já enuncia e esta reforça: versão publicada é
imutável. Corrige-se com X.Y.Z+1, nunca republicando o mesmo número.
Todo PR que muda comportamento traz um arquivo em .changes/, e é ele que declara o impacto
e escreve o texto que o operador vai ler.
---
impacto: nada_mudou # nada_mudou | capacidade_nova | exige_acao
secao: corrigido # adicionado | alterado | corrigido
titulo: A IA avisa o cliente antes de chamar uma pessoa
---
Quando o atendimento automático parava e a conversa ia para a fila humana, o
cliente não recebia mensagem nenhuma: ele falava, e ninguém respondia.O campo é o efeito, não o número — e essa escolha é a régua inteira. Pedir
impacto: minor convidaria de volta exatamente o erro que este documento existe para
corrigir, porque "minor" é a resposta, e responder a resposta é o que se faz quando não
se tem a pergunta. nada_mudou não tem como ser respondido errado por quem sabe o que
fez. O número sai de BUMP_DO_IMPACTO, em lib/release/fragmento.ts, e a régua está
presa por valor no teste ao lado — inverter a tabela reprova o CI.
Quando impacto: exige_acao, o fragmento traz também um bloco ## Requer atenção com o
que o operador precisa fazer; sem ele o fragmento é recusado, e o contrário também
(aviso com impacto mais brando). Quem emite o ### ⚠️ Requer atenção que a tela procura é
o montador, uma vez só.
Para conferir o que os fragmentos de agora produziriam, sem escrever nada:
pnpm release:conferirTrês propriedades, e cada uma resolve um defeito medido:
- Um arquivo por PR, nome único. Dois PRs paralelos criam dois arquivos diferentes, e o
conflito de merge deixa de ser possível por construção. Medido: oito merges com
conflito real no
CHANGELOG.md, todos concentrados em 25–26/08 — a mesma janela dos três minors em três dias. - Escrito por quem fez a mudança, quando fez. O texto para de ser reconstruído do
git logdias depois por quem não estava lá. Medido: 13 de 45 commits entre a v1.5.0 e a v1.6.0 não têm eco na seção da versão. - O impacto é declarado, não inferido. Derivar de mensagem de commit não funcionaria aqui: medido, 94,4% dos commits sem merge seguem Conventional Commits, mas apenas 2,2% dos merges — e há zero marcações de breaking change em 2.639 commits, o que tornaria major inderivável.
release-please e semantic-release montam o changelog a partir de mensagens de commit.
Aqui isso não serve, e a razão é do produto, não de gosto: o CHANGELOG.md é tela.
lib/system/changelog.ts extrai a seção de uma versão e a rota de sistema a entrega ao dono
da VPS. É prosa longa em português — média de cerca de cem linhas por versão — escrita para
quem não leu o diff. Nenhum gerador produz isso a partir de fix(scope): ....
O que se adota dessas ferramentas é a ideia — fragmento por PR, número derivado, release por PR acumulativo —, não o gerador de texto.
Não está escrita aqui, e isso é deliberado: afirmação de versão envelhece a cada release, e
foi assim que AGENTS.md passou seis minors dizendo 1.0.0. Comando não envelhece:
git ls-remote --tags --refs origin 'refs/tags/v*' \
| sed 's#.*refs/tags/v##' | awk '!/-/' | sort -V | tail -1O awk '!/-/' descarta prerelease e tag de fork — o repositório carrega v1.1.1-jmpo.1 e
jmpo/v1.4.0, que existem para não colidir com a numeração daqui.
E o package.json não é a fonte: ele segue em 0.1.0, de propósito. A fonte é a tag
v* mais a seção do CHANGELOG.md.
- O número responde ao operador, não ao autor. A pergunta é sempre o que ele precisa fazer.
- Ninguém digita o número. Ele é calculado a partir dos fragmentos declarados.
- A tag nasce no CI, de commit contido na
main. Nunca da máquina de alguém. - Versão publicada é imutável. Conserto é
X.Y.Z+1; nunca republicar o mesmo número. - Todo PR que muda comportamento traz seu fragmento. Sem ele, o texto que chega ao operador é reconstruído por quem não estava lá — ou não chega.
- Bump não pode exigir edição manual de arquivo na VPS. Se exigir, é major e vem com plano de migração.