Files
DeskcommCRM/CONTRIBUTING.md
T
Rafael MelgaçoandClaude Opus 5 b123827687 docs: a auditoria vira arquivo versionado, e os retratos param de fingir que são o presente
A varredura estava em /private/tmp — 242 KB de trabalho medido que sumiria no
próximo restart. Agora é `docs/audits/2026-08-14-afirmacoes-de-estado.md`, com o
COMANDO de cada achado preservado. É o comando que faz o documento valer: quem for
tratar um achado mede de novo em vez de confiar na linha. O relatório envelhece
exatamente como aquilo que ele critica, e diz isso no topo.

Junto, a evidência do alinhamento do canal `stable`
(`2026-08-14-alinhamento-stable-v1.3.0.md`): os três pacotes tinham `stable` e
`1.3.0` em digests diferentes, e agora batem. Medido ANONIMAMENTE depois, com token
de pull público — escrita que só se confirma pela sessão que a fez não está
confirmada. `latest` e `main` conferidos como controle: intactos.

## A triagem é por TIPO de documento, não por gravidade

Foi a chave que faltava, e ela inverte o que eu faria sozinha: gravidade alta num
documento-retrato **não** se conserta.

**RETRATO** (`current-state.md`, `harness-audit.md`) — carimbados, não corrigidos.
Os dois já traziam `audited_against: 789dfa6` no front-matter, e ninguém lê
front-matter: o corpo falava no presente. O carimbo agora é a primeira coisa
visível, com a distância medida — **1.014 commits e 71 migrations** entre o commit
auditado e hoje — e declara que nada ali é mantido. Documento honestamente datado
nunca mente; documento atualizado uma vez volta a mentir na semana seguinte, e pior,
porque a atualização recente faz confiar mais.

**AUTORIDADE** — corrigidos, e onde deu, a afirmação virou COMANDO:

- os três READMEs diziam "**Quatro checks são obrigatórios**". São cinco. Em vez de
  trocar o número, entrou o `gh api … --jq '.required_status_checks.contexts'` com a
  saída do dia — e a admissão de que a linha já disse "quatro" e "cinco";
- os três READMEs mandavam ter o Google Authenticator à mão porque "o primeiro login
  de admin exige MFA". **Não exige** — o `bootstrap-owner.ts` grava `mfa_required:
  false` explícito. Um comprador espera uma tela que não vem;
- os três diziam que o comando local era "a lista **completa** dos gates
  obrigatórios". Não é: `e2e` e `imagens-ok` só rodam no CI. Verde na sua máquina
  não é verde no merge;
- `AGENTS.md` afirmava que **6 vars de `lib/env.ts` faltam no `.env.example`,
  incluindo 3 secrets**. Medido: das 45 chaves, a única ausente é `NODE_ENV`, e os
  três secrets nomeados estão todos lá. A regra do DoD continua; a dívida caiu;
- `AGENTS.md` também dizia que login/signup/convite estavam sem rate limit. O
  `lib/auth/rate-limit.ts` cobre os quatro, por IP e por identificador hasheado.
  Crons e MCP seguem sem, e isso ficou escrito;
- `AGENTS.md` prometia um "DoD de 15 itens" — agora são 16, e a linha virou o
  `grep` que conta, em vez de um número que envelhece de novo no próximo item.

**PLANEJAMENTO** (`docs/stories/`, epics) — intocado. É onde vivem 385 dos ponteiros
mortos do repo, e registro histórico pode apontar para o que nunca existiu. O gate
já exclui essa pasta de propósito.

## O processo — duas linhas, não um sprint

1. `CONTRIBUTING.md`: quem toca um doc de autoridade corrige as afirmações **daquele**
   doc. Não sai caçando nos outros — a dívida decai sozinha se ninguém a alimentar.
2. `CLAUDE.md`, DoD item 16: se o PR muda comportamento, procurar a afirmação de
   estado sobre esse comportamento. Só sobre o que mudou. E onde puder virar comando,
   trocar em vez de corrigir.

## Um erro de instrumento, no meio disto

O script com que filtrei os achados "que passam de afirmação envelhecida" imprimia
sempre a MESMA citação — variável de loop vazando para o loop seguinte. As saídas
medidas estavam certas, as citações não. Reescrito e refeito: 25 achados, não os 43
que a primeira passada listou. Um instrumento quebrado não devolve erro; devolve uma
lista plausível.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKm2XcTcfgi1vdMcDYSRWa
2026-08-14 17:54:26 -03:00

8.0 KiB

Contributing — DeskcommCRM

Antes de começar

  1. Leia CLAUDE.md — convenções não-negociáveis.
  2. Leia ARCHITECTURE.md — visão de 1 página.
  3. 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:

  1. Atualizar frontmatter status: pending → completed (partial: ...) ou status: completed.
  2. Append "Wave Completion Log" no final do arquivo.
  3. Atualizar a row correspondente em docs/stories/epics/MASTER.md.

PR process

  1. Branch a partir de main.

  2. Implementar. Adicionar testes (E2E pra fluxos, unit pra lógica pura).

  3. 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 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 baseline
    

    O 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>_all se 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.log esquecido (use lib/logger.ts). O pnpm lint não reprova isso — a regra está como aviso, então ele passa verde; a conferência é humana
    • Env vars novas em .env.example e lib/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 no supabase/baseline.sql e linha no MANIFEST.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*.yml ou hostgator-setup-kit/: a mudança alcança quem já instalou. Lei em docs/doctrine/packaging.md. O CI reprova serviço build:-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 .env antigo, 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
  4. Abrir PR contra main. Description deve referenciar o epic e listar evidências (logs/screenshots dos testes).

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

  6. CI deve passar antes de merge. Obrigatórios: verify, invariants (isolamento RLS), build-and-size, e2e e imagens-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 e2e não é "jornada provada": ele mesmo imprime, no resumo, quais specs não cobriu — e a que fica de fora é justamente vps-fresh-onboarding, a instalação do zero.

    Esta lista dizia "três obrigatórios" e chamava o e2e de 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.

  1. 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.
  2. Issue com pessoa atribuída não se duplica. Se você quer ajudar mesmo assim, comente oferecendo; não abra PR concorrente.
  3. Mantenedor não implementa issue marcada good first issue ou help wanted sem 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.
  4. 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

Duas coisas vão parecer erro seu e não são:

  • O check Vercel fica vermelho com "Authorization required to deploy". A main deste repositório faz deploy de produção, e a Vercel se recusa a construir PR de fork por segurança — o que está certo. Ignore esse check; ele não entra no gate de merge.
  • 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.

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.

Anti-patterns proibidos

Lista completa em CLAUDE.md. Os mais letais:

  • Trigger Postgres fazendo HTTP
  • Service role usado em handler sem filtrar organization_id manualmente
  • getSession() no backend (use getUser())
  • API key em query string
  • Bearer plaintext no DB
  • console.log em 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.