Refs: #1841, #890 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
13 KiB
Contributing — DeskcommCRM
🇧🇷 Português · 🇺🇸 English
Antes de começar
- Abra o repositório no seu assistente de código (Claude Code, Codex, Cursor, OpenCode ou
Antigravity): o guia
deskcomm-contribuir(.agents/skills/deskcomm-contribuir/SKILL.md) mede antes do PR o que a triagem mede depois — branch atrasada, tripla de migration, marca do fork no diff, fragmento de release — e arma os hooks de git combash .agents/skills/deskcomm-contribuir/scripts/armar-hooks.sh. Para ter os guias em qualquer pasta:bash scripts/instalar-guias.sh. Vai editar um guia? Rodebash scripts/instalar-guias.sh --fonte .no seu clone — no Claude Code a skill global vence a do projeto, e sem isso você testaria a versão damain, não a sua. - Leia
CLAUDE.md— convenções não-negociáveis. - Leia
ARCHITECTURE.md— visão de 1 página. - Identifique o epic de origem em
docs/stories/epics/MASTER.md.
Fluxo
Branches
feat/EPIC-XX-short-slug # nova feature
fix/EPIC-XX-short-slug # bug fix
chore/short-slug # chore (deps, configs)
docs/short-slug # apenas docs
Commits
Conventional commits + escopo EPIC-XX:
feat(EPIC-04): kanban drag-and-drop com fractional indexing
fix(EPIC-03): cron recover-stuck-messages marcando sending stuck >5min como failed
docs(EPIC-12): mark complete + wave log
Mensagens em PT-BR são aceitas. O assunto deve ser imperativo e ≤72 chars.
epic-executor
Mudanças grandes seguem docs/stories/epics/. O epic-executor consome o frontmatter (epic_id, priority, depends_on, status) e executa wave-by-wave com validação E2E continuous.
Ao finalizar um epic:
- Atualizar frontmatter
status: pending → completed (partial: ...)oustatus: completed. - Append "Wave Completion Log" no final do arquivo.
- Atualizar a row correspondente em
docs/stories/epics/MASTER.md.
PR process
-
Branch a partir de
main. -
Implementar. Adicionar testes (E2E pra fluxos, unit pra lógica pura).
-
Definition of Done. A lista está separada em duas por um motivo: até hoje ela misturava o que uma máquina reprova com o que só uma pessoa percebe, e contribuidor marcava o checklist inteiro de boa-fé para ser barrado por um gate que ninguém tinha contado a ele.
O que o CI reprova sozinho — rode antes de abrir o PR e não terá surpresa:
pnpm cercas # ~30 s: as guardas estruturais (baseline, MANIFEST, docs, workflows, espanhol do i18n, fragmentos de .changes/) — o que mais reprova PR pnpm typecheck && pnpm lint && pnpm lint:channels && pnpm test:unit && pnpm test:shell && pnpm build pnpm test:db # precisa de Docker; sobe um Postgres limpo e aplica o baselineO que o CI NÃO vê — fica com você e com a revisão, e é onde moram os defeitos caros:
- RLS habilitada e policy
tenant_isolation_<tabela>_allse você criou tabela tenant-aware (o teste de isolamento cobre uma lista fixa de tabelas; a sua nova não entra sozinha) - Audit log emitido se há mutação relevante
- Rate limit aplicado se a rota é pública
- Zod validando todo input externo
- Sem
console.logesquecido (uselib/logger.ts). Opnpm lintnão reprova isso — a regra está como aviso, então ele passa verde; a conferência é humana - Env vars novas em
.env.exampleelib/env.ts, com default que não quebre instalação nova - Mudança de schema saiu como tripla: arquivo em
supabase/migrations/, apêndice idempotente nosupabase/baseline.sqle linha noMANIFEST.md. O kit self-host aplica só o baseline — migration que não chega lá não chega em quem instalou numa VPS. Nenhum job de CI confere isso - Se você tocou
Dockerfile*,docker-compose*.ymlouhostgator-setup-kit/: a mudança alcança quem já instalou. Lei emdocs/doctrine/packaging.md. O CI reprova serviçobuild:-only, instalação em tag móvel e imagem quebrada (imagens-ok); o que fica com você é o resto: variável nova com default que não quebre.envantigo, e a atualização não pedindo edição manual de arquivo. Nenhum bump pode exigir que o operador da VPS edite alguma coisa na mão — se exigir, abra issue com plano de migração em vez de PR - Docs atualizadas se mudou contrato (PRD/spec)
pnpm test:e2e(subset relevante) — opcional se você contribui de fora, ver abaixo
- RLS habilitada e policy
-
Abrir PR contra
main. Description deve referenciar o epic e listar evidências (logs/screenshots dos testes). -
Tocou um documento de autoridade? Corrija as afirmações de estado daquele documento — as que dizem o que está ativo, o que falta, o que aponta para onde. Não saia caçando nos outros: a dívida decai sozinha se ninguém a alimentar. Achados medidos, com o comando de cada um, em
docs/audits/2026-08-14-afirmacoes-de-estado.md. -
CI deve passar antes de merge. Obrigatórios:
verify,invariants(isolamento RLS),build-and-size,e2eeimagens-ok.O
imagens-ok(em.github/workflows/publish-image.yml) constrói as três imagens que o self-hoster instala, roda em PR e bloqueia desde 2026-08-13.Verde no
e2enão é "jornada provada": ele mesmo imprime, no resumo, quais specs não cobriu. Quais são, leia do próprio workflow em vez de desta linha — ela já disse que a de fora eravps-fresh-onboarding, a instalação do zero, e desde o PR #983 essa roda no CI:git show origin/main:.github/workflows/e2e.yml | grep -A4 'FORA_DO_CI:'E mesmo a jornada que TEM gate continua devendo a prova pela tela quando você mexe nela (DoD 12): gate prova que não regrediu, não que a experiência ficou boa.
Esta lista dizia "três obrigatórios" e chamava o
e2ede não-bloqueante. Estava desatualizada nos dois pontos, e quem a usasse como régua mediria contra a régua errada. Confira na fonte antes de confiar em qualquer lista escrita:gh api repos/melgarafael/DeskcommCRM/branches/main/protection --jq '.required_status_checks.contexts'
Pegando uma issue — o protocolo
Existe porque já falhamos nisto: em 2026-07-30 abrimos uma issue, um contribuidor começou a resolvê-la, e um mantenedor entregou a mesma correção 21 segundos antes sem que nenhum dos dois pudesse ver o outro. O trabalho dele foi para o lixo. As regras abaixo são para que isso não se repita.
- Comente "pego esta" antes de codar. Uma linha basta. Um mantenedor te atribui a issue — a partir daí ela é sua e ninguém mais mexe.
- Issue com pessoa atribuída não se duplica. Se você quer ajudar mesmo assim, comente oferecendo; não abra PR concorrente.
- Mantenedor não implementa issue marcada
good first issueouhelp wantedsem antes se atribuir a ela publicamente. Se você vir uma dessas sem dono, ela é sua para pegar — essa é a garantia que damos em troca do passo 1. - Sem resposta em 48h depois do "pego esta"? Comece assim mesmo e diga no PR. A demora é nossa, o custo não pode ser seu.
Se você está contribuindo de fora (fork) — leia isto
Uma coisa vai parecer erro seu e não é:
- Os workflows ficam parados esperando aprovação no seu primeiro PR. É política do GitHub para quem nunca contribuiu antes. Um mantenedor libera; do segundo PR em diante roda sozinho. Se demorar, comente no PR.
Abra o PR de um ramo com nome, nunca do main do seu fork. Se o main do fork já tem
personalizações suas — e ele quase sempre tem, porque é dele que a sua VPS puxa —, o PR propõe
essas personalizações ao produto inteiro. Isso não gera conflito e não acende gate nenhum: elas
entram em silêncio para todas as instalações. Foi medido (PR #465): sete arquivos com a marca de um
cliente, seis deles mergeando sem um único conflito. O caminho é git checkout -b fix/o-que-voce-conserta
a partir da main deste repositório, com só o seu conserto dentro.
Com "Allow edits by maintainers" ligado no seu PR, o projeto pode empurrar um conserto direto na
branch do PR — um ajuste mecânico, ou a main trazida para dentro quando há conflito. Sempre como
commit novo: nunca --force, nunca rebase, e os seus commits ficam como estão. Avisamos no PR antes
de empurrar. Quando isso acontecer, traga a branch antes de continuar (git pull --no-rebase) e só
então empurre de novo; um --force do seu lado apagaria o que foi empurrado do lado de cá. Com a
opção desligada, o conserto vai numa branch nossa. Nos dois caminhos, o trabalho que é seu entra com
você como autor.
A marca da sua instalação não se troca editando código. Não altere DEFAULT_APP_NAME em
lib/branding.ts, nem os títulos em app/. O banco manda (platform_branding,
organizations.settings.branding), APP_NAME no .env é a semente que o install.sh pergunta, e
o resto é a tela Configurações › Marca. Receita inteira em docs/white-label.md.
Editar a constante troca o padrão do PRODUTO — e a sua marca some no próximo git pull, o que é a
razão prática de o caminho suportado ser melhor para você também.
E sobre o pnpm test:e2e do DoD: rodar a suíte completa exige Docker, banco semeado e WAHA
local. Não travamos PR externo nisso — mande o que conseguiu provar (unit + descrição do
que testou na mão), que a prova de tela fica com o mantenedor. Exigir prova sem entregar a
ferramenta de produzi-la seria pedágio, não rigor.
tests/invariants/ é congelado — e isso vale para o COMPORTAMENTO, não só para o arquivo
Os arquivos de tests/invariants/ guardam leis do produto, e mexer neles pede justificativa
escrita. Duas coisas que não estão óbvias e já custaram tempo a quem contribui:
- O guarda é um hook local do mantenedor (
core.hooksPath=loop/hooks), não um check do CI. Você não vai vê-lo reprovar no seu fork — o que você vê é a integração travar depois. - Um PR pode reprovar um invariante sem tocar no arquivo dele. Se o seu conserto muda o comportamento que a lei afirma, o vermelho aparece lá. Isso não é um descuido seu — é o sinal de que existem duas regras concorrentes, a que está escrita e a que você propõe.
Quando acontecer, não apague nem afrouxe a asserção: diga no PR qual é a sua razão e deixe a escolha explícita. Quem tria escreve a mudança do invariante com a justificativa exigida, ou ajusta o conserto para preservar a lei antiga — e a decisão fica registrada no PR, que é onde ela serve para a próxima pessoa.
Texto de tela: toda frase nova precisa do espanhol
O produto fala português e espanhol, e o CI reprova frase nova sem tradução. A regra não estava escrita aqui até 16/09/2026, e um PR de primeira contribuição foi reprovado por ela — a falha era nossa, não de quem contribuiu.
Se você acrescentou uma frase que aparece na tela, ela passa por t("...") e ganha uma linha
em lib/i18n/dicionario.ts:
"Digite o identificador do modelo": { es: "Escribe el identificador del modelo" },
A chave é o texto em português (não um código). Só o espanhol precisa de linha; o resto degrada para o português de propósito.
Para conferir antes de abrir o PR, sem rodar a suíte inteira:
pnpm test:unit tests/unit/i18n-espanhol-cobre-a-tela.test.ts
Ele reprova nas duas direções: chave usada na tela sem espanhol, e prosa em português que não
passou por t(). Se você não fala espanhol, mande assim mesmo e diga no PR — a tradução é
trabalho de dez segundos para quem tria, e não é motivo para segurar um conserto.
Anti-patterns proibidos
Lista completa em CLAUDE.md. Os mais letais:
- Trigger Postgres fazendo HTTP
- Service role usado em handler sem filtrar
organization_idmanualmente getSession()no backend (usegetUser())- API key em query string
- Bearer plaintext no DB
console.logem código merged
Setup local
Veja README.md §Como rodar local.
Suporte
GitHub Discussions — é o canal público, funciona para qualquer pessoa e é onde a resposta fica registrada para quem vier depois. Para bug, abra uma issue.
Se for algo que não cabe em público (segurança, por exemplo): rafael@maudibrasil.com.br — o mesmo
endereço do CODE_OF_CONDUCT.md.
Esta seção apontava para um Discord interno cujo convite mora num Notion privado — inalcançável justamente para quem mais precisava dela, que é quem vem de fora. Ficou aqui como lembrete de que canal de suporte se testa pelo lado de fora.