Fluxo de trabalho
Como uma task anda do início à entrega. Somos várias pessoas — e vários agentes — no mesmo código, em três papéis: quem desenvolve abre a task, trabalha numa branch própria e pede revisão; quem revisa lê o trabalho sem ter participado dele e diz se está no padrão; quem integra leva o que foi aprovado ao develop, ao staging e, no corte de versão, ao main. Vale para pessoas e para agentes (Claude e outros), e não depende do GitLab nem do GitHub: o fluxo é do repositório e da ferramenta de tasks (taskin), não da hospedagem.
Por que assim
- A
developé de todos. O que entra nela vai para o trabalho dos outros. Uma branch por task mantém o que está pela metade fora do caminho de quem trabalha em outra coisa. - Pouca coisa aqui é local. O Directus é gerado a partir dos tipos: um campo, um nome ou uma permissão muda o banco, as extensões e as policies. A revisão é onde isso aparece antes de chegar na
develop. - Quem revisa não é quem fez. Uma revisão feita por quem escreveu o código confirma o que o código faz, não o que ele deveria fazer. Por isso a revisão é de uma pessoa — ou de uma sessão de agente — que só conhece a task e o padrão do repositório.
- O que publica é protegido; o que integra, não. O
maindispara versão e publicação. Entre ele e adevelophá astaging, onde cada task vira um commit só: é o que se lê para gerar a versão e o que se promove quando a versão é cortada. - Vale para todo mundo, inclusive para quem revisa: o código de @sidartaveloso também passa pelo mesmo caminho quando outra pessoa estiver no papel de revisor.
O fluxo é combinado, não imposto pela hospedagem. Por isso ele está escrito, a ferramenta o protege onde dá (taskin, pre-push), e os agentes o seguem.
As três branches
| Branch | O que é | Como se escreve nela | Quem |
|---|---|---|---|
develop | Integração. Histórico completo, com os commits de TDD. | Merge direto da branch da task, depois da revisão: chore: integra feat/task-NNN no develop. | quem integra |
staging | Candidato a entrega. Um commit por task. | Promoção: git cherry-pick -m 1 do merge commit da task, com a mensagem conventional da task. A tag task-NNN nasce aqui. É o done. | quem integra, pelo comando |
main | Lançado. Dispara versão e publicação. | Só no corte de versão: recebe a staging por fast-forward. O histórico do main é os squashes das tasks — é o que o gerador de versão lê. | quem integra, no corte de versão |
Nomes, de propósito: staging é a environment branch permanente (GitLab Flow), e já é o nome da imagem que sai dela (staging-latest). Não é release/x.y (branch temporária do Git Flow, apagada após a versão) nem next/canary (canal de pré-lançamento npm).
O ciclo de uma task
Três coisas que o diagrama diz e vale repetir:
- A revisão devolve, não corrige. O achado volta para quem desenvolve, que corrige na mesma branch e pede revisão de novo. Duas pessoas (ou duas sessões) escrevendo na mesma branch é como um achado se perde.
donequer dizer "está nostaging", não "o código ficou pronto". Entre o fim do desenvolvimento e odonehá a revisão e a promoção.- Ninguém escreve no
mainpor task. Omainrecebe astaginginteira, no corte de versão, por fast-forward. É o único momento em que versão e publicação acontecem.
Estados da task
| Estado | Quem põe | Comando | O que precisa ser verdade antes |
|---|---|---|---|
pending | quem cria | pnpm taskin new | — |
in-progress | quem desenvolve | pnpm taskin start NNN | develop limpa e igual à remota |
in-review | quem desenvolve | pnpm taskin review NNN | branch de task; pnpm lint e pnpm typecheck verdes |
done | quem integra | pnpm taskin finish NNN | revisão aprovada; merge no develop feito; promovida para staging |
Passo a passo
1. Pegar a task
git checkout develop && git pull
pnpm taskin list
pnpm taskin start 047
git checkout -b feat/task-047- Tasks novas só com
pnpm taskin new(o taskin numera); nunca escrevendo o.mdà mão. - O
taskin startgrava o estadoin-progressna branch atual. Rode-o com adeveloplimpa e igual à remota, antes de criar a branch; se adevelopestiver em checkout em outro worktree, crie a branch a partir dela (git switch -c feat/task-047 origin/develop) e rode otaskin startnela. - Uma branch por task, com o nome
feat/task-NNN(oufix/task-NNN). Não use branches longas que juntam várias tasks.
2. Trabalhar na branch
Teste antes do código. O teste vermelho entra num commit; a implementação que o deixa verde entra no seguinte. Um teste escrito depois valida o que o código faz, não o que ele deveria fazer.
Commits pequenos, no padrão
tipo(escopo): descrição, em português, como nogit log.pnpm lint,pnpm typecheck,pnpm formatepnpm testtêm de passar. Erro se corrige na hora; nada de--no-verify,noqaou@ts-ignore.Decisão técnica que muda modelo, contrato ou arquitetura vira um RDT em
docs/RDT/(ver oREADME.mdde lá), e espera a aprovação de quem revisa antes da implementação.Para trazer o que entrou na
developenquanto você trabalhava:bashgit fetch origin && git merge origin/develop
3. Pedir revisão
pnpm taskin review 047Um comando só. O taskin review:
- confere que você está numa branch de task. Se não, explica e para, com a task ainda em
in-progress; - roda
pnpm lintepnpm typecheck. Se algum falhar, a task também não vai para revisão: corrija e rode de novo; - passa a task para
in-reviewe commita o doc dela.
Depois disso, avise quem integra que a task está pronta para revisão — e pare. Quem desenvolve não faz merge na develop, não promove e não roda taskin finish. O doc da task precisa dizer o que mudou, por quê, e como testar (comandos, tela, hardware usado); se mexeu no modelo do Directus, diga.
4. Revisar
Quem integra chama quem revisa — uma pessoa que não participou da task, ou uma sessão de agente sem o contexto da implementação. O brief é mínimo de propósito, e é sempre o mesmo:
Revise a task 047 pelo padrão do repositório.
Escreva o resultado em TASKS/assets/revisao-task-047.md.Mais que isso já contamina: dizer "confira se o cherry-pick está certo" entrega a conclusão junto com a pergunta. O que a revisora precisa saber está no doc da task, no CLAUDE.md e nas RDT — e o modelo do arquivo que ela escreve está em TASKS/assets/modelo-revisao-task.md.
Quem revisa:
- lê o doc da task, o
CLAUDE.md, as RDT que a task cita e o diff da branch contra adevelop; - escreve
TASKS/assets/revisao-task-047.md: cada achado com severidade (bloqueia / deveria / sugestão),arquivo:linha, o que está errado, por que importa e a correção proposta; - roda os gates dos pacotes tocados (
test,typecheck,lint,format:check), otest:dockere o e2e quando a task alcança o que eles cobrem; - não corrige. Com achado que bloqueia, devolve: quem desenvolve roda
pnpm taskin start 047, corrige na mesma branch e pede revisão de novo.
O critério que libera o finish: o TASKS/assets/revisao-task-047.md existe, traz um veredito explícito, e nenhum achado de severidade bloqueia continua de pé. Achado "deveria" e "sugestão" não seguram a task — viram item no doc dela ou task nova, e isso fica escrito no veredito. Sem o arquivo da revisão não há aprovação: a conversa não é a revisão, e o que não está no repositório some junto com a sessão.
5. Integrar
Com a revisão aprovada, quem integra:
# no develop
git merge --no-ff feat/task-047 -m "chore: integra feat/task-047 no develop"
# promoção para o staging: um commit só, a tag, e a branch some
pnpm --filter @geocontrol/ferramentas promover-para-staging 047 --simular
pnpm --filter @geocontrol/ferramentas promover-para-staging 047
pnpm taskin finish 047A promoção faz, pela ordem: git cherry-pick -m 1 do merge commit da task, sem commitar; o commit único na staging, com a mensagem conventional da task (feat(task-047): …, tirada do - Tipo: e do título do doc); a tag anotada task-047 nesse commit; push de staging e da tag juntas (git push --atomic) para cada remoto de entrega; exclusão da branch da task nesses remotos; volta para a develop e exclusão da branch local. Com --simular ela só mostra o plano, sem rodar nada.
Os remotos de entrega são origin e github, escritos no comando, não deduzidos do git remote. Ali convivem espelhos e checkouts de trabalho de outras máquinas (hoje há um desktop apontando para outra estação): deduzir a lista publicaria a entrega numa máquina pessoal e apagaria a branch da task lá. Para outra lista, --remoto origin --remoto github ou GEOHUB_REMOTOS=origin,github.
Ela recusa antes de começar o que não daria certo: fora da develop, árvore suja, staging que não existe aqui, tag task-047 que já existe (a task já foi promovida), merge da task que não está na develop, mais de um merge citando a task (aí ela lista os candidatos e pede --merge <sha>), remoto de entrega que não responde, e staging local atrás da de algum remoto — que é o caso que deixaria a entrega divergente entre eles. Se um passo falhar — um conflito no cherry-pick —, ela para ali e diz como sair do meio.
A busca do merge é por primeiro pai: sem isso ela acharia também o git merge origin/develop que o passo 2 ensina a fazer dentro da branch — o assunto que o git escreve sozinho cita a task —, e a promoção recusaria por ambiguidade no caminho normal. Medido na develop: 49 das 150 tasks com merge lá teriam mais de um candidato; por primeiro pai, 19.
Essas 19 são integrações múltiplas de verdade — a mesma branch mesclada várias vezes, ou branches diferentes para a mesma task. Para elas nenhum cherry-pick isolado leva o trabalho inteiro, e o --merge <sha> produz um squash incompleto sob a mensagem da task. Aí a entrega se monta da ponta da branch, não de um merge: git merge --squash feat/task-NNN na staging, com a mensagem conventional da task, e a tag à mão. Uma branch e uma integração por task evitam o caso; devolução de revisão depois do merge e segunda branch para a mesma task o recriam.
A ordem importa num ponto medido: a branch local só é apagada depois de voltar para a develop. De dentro da staging o git branch --delete recusa, porque o commit da promoção é um squash e a branch não é ancestral dele.
6. Cortar versão
Quando o staging tem o que se quer lançar, o main o recebe por fast-forward e ganha a tag de versão. É o único passo que dispara versão e publicação, e ele é do roteiro de release — nunca de uma task.
git checkout main && git merge --ff-only staging
export GEOHUB_CORTE_DE_VERSAO=1
pnpm version # lerna version --conventional-commits
git push github main --tags # o lerna só empurra para o remoto dele
unset GEOHUB_CORTE_DE_VERSAOO --ff-only é a conferência: se ele recusa, alguém escreveu no main fora do corte, e isso se resolve antes de seguir. O GEOHUB_CORTE_DE_VERSAO=1 é o que o pre-push pede para deixar o main ir (ver abaixo).
A variável é exportada antes do pnpm version, e não só na linha do push, porque quem empurra o main é o próprio lerna: ele faz o push do commit de versão e das tags ao terminar (--no-push é o que desligaria isso, e o lerna.json não desliga). Pondo a variável só na linha seguinte, o lerna falha no pre-push depois de já ter escrito o commit de versão, as tags e o CHANGELOG — no passo mais caro do fluxo.
O lerna empurra para o remoto dele (origin), um só. O github é espelho e recebe o main e as tags na linha seguinte: se só um remoto tivesse o main, o espelho deixaria de ser espelho justamente no que o fluxo diz não depender da hospedagem.
O que o main dispara, e por isso ele é protegido:
- a versão:
pnpm versionélerna version --conventional-commits, que lê os assuntos conventional do histórico para decidir o próximo número e escrever oCHANGELOG. É por isso que astagingtem um commit por task, com o tipo da task no assunto: é esse histórico que omainrecebe, e não os commits de TDD dadevelop; - a publicação:
pnpm publish(lerna publish from-package), e o.gitlab-ci.yml, cuja.rules-templateroda a esteira de pacote empushnomaine em$CI_COMMIT_TAG.
A imagem :staging-latest não sai daqui: ela é do ambiente de staging, e quem a gera é o build do packages/deploy (comporTags, pelo ambiente), rodado à mão. O nome da branch e o da tag da imagem coincidem de propósito, mas são coisas diferentes.
A staging nasce uma vez, do main
git fetch origin && git branch staging main
GEOHUB_PROMOCAO=1 git push origin staging
GEOHUB_PROMOCAO=1 git push github stagingDepois disso ela é permanente: só a promoção escreve nela, e o main a recebe por fast-forward no corte.
O pre-push protege as duas
O fluxo não depende da hospedagem: proteção de branch do GitLab ou do GitHub não serve, porque o repositório já trocou de host uma vez e os dois remotos convivem hoje. Quem protege é o .husky/pre-push, que recusa:
| push | passa quando |
|---|---|
staging | GEOHUB_PROMOCAO=1 — o promover-para-staging põe sozinho |
main | GEOHUB_CORTE_DE_VERSAO=1, à mão, no corte de versão |
apagar staging ou main | nunca |
É um guarda contra o engano, não contra quem quer burlar: quem põe a variável à mão está dizendo que sabe o que está fazendo. As provas de que ele recusa estão em scripts/pre-push-protege-entrega.test.mjs (pnpm test:scripts).
Para agentes
- Siga este fluxo também quando a pessoa pedir só "termine a task": pare na task em
in-review, com o aviso a quem integra. - A sessão que desenvolveu não inicia a sessão que revisa: quem a inicia é a sessão principal, para o relatório chegar a ela. A revisora nasce sem contexto da implementação, de propósito.
- Não faça merge na
develop, promoção parastagingnemtaskin finishsem que quem integra peça. Nunca escreva nomain. - Push só com autorização da pessoa com quem você está trabalhando.
taskin finishem modo autopilot commita tudo o que estiver na árvore, inclusive arquivo de outra pessoa: árvore limpa antes, sempre.