Skip to content

Quality Gates ​

Documenta os checks automáticos que protegem a qualidade do código. Atualizado em: 11/set/2026 — pre-commit vira o tier rápido; suíte unitária + integração passam para o pre-push (big-fat-refactor-03, Lane A).

Fonte da verdade ​

Cada gate vive uma única vez no registry scripts/gates.registry (id|stages|command|scope|tier|category|blocking). Desde #638 o registry é doc-only — o runner scripts/gates.sh foi removido e nenhum runner executa os gates declarados aqui em runtime. O pre-commit roda os gates via bash scripts/quality-gates.sh <docs|config|code> (runner linear — ver .husky/pre-commit); o pre-push chama o mesmo runner com a categoria push; o CI chama o comando de cada gate diretamente por job (ver .github/workflows/ci.yml, cujo header mapeia job → gates).

RunnerOnde roda
bash scripts/quality-gates.sh <docs|config|code>Pre-commit (.husky/pre-commit) — gates adaptativos por categoria
bash scripts/quality-gates.sh pushPre-push (.husky/pre-push) — tier pesado local: code + contagem de testes + unit + integração
Comandos diretos por jobCI (.github/workflows/ci.yml) — gate pesado autoritativo

Cada commit passa por 3 camadas de verificação antes de chegar à produção:

[Commit] → pre-commit (gates rápidos por categoria) → [Push] → pre-push (changelog + drift + secret scan + suíte unitária + integração) → CI (gate pesado: lint, typecheck, unit, build, integração, E2E smoke, docs) → [Deploy manual]

Pré-commit (local — .husky/pre-commit) ​

Roda a cada git commit. Bloqueia o commit se falhar. Gates adaptativos por categoria do diff: docs roda só os always; config adiciona typecheck; code adiciona o registro de error codes. A suíte unitária e a contagem de testes não rodam aqui — vivem no pre-push (ver abaixo), para que fazer um commit pequeno não seja a ação mais lenta do repositório.

Nível 0 — todas as categorias (rápidos) ​

GateComandoO que verifica
Lint-stagednpx lint-stagedBiome apenas nos arquivos staged
Biomepnpm lintCódigo inteiro (formatação + lint)
as any regressionbash scripts/count-as-any.shBaseline em scripts/thresholds.sh
Routes SQL gatebash scripts/no-sql-in-routes.shSem SQL literal em rotas (#618)
Memo hooksbash scripts/count-memo-hooks.shSem useMemo/useCallback manuais novos
Boundary lintbash scripts/boundary-lint.shImportações entre camadas
Portability boundarybash scripts/no-cloudflare-in-core.shSem runtime Cloudflare em workers/src/{domain,application,ports}; ratchet em {services,events} (ADR-0044)
Import lintbash scripts/import-lint.shRestrições de import em routes/
getDB call sitesbash scripts/count-getdb.shContagem de getDB() no baseline
SQL×schema lintbash scripts/schema-sql-lint.shSQL inconsistente com o schema
Docs lintpnpm docs:lintmarkdownlint
Index completenessbash scripts/check-index-completeness.shTodo .md listado no index.md da pasta
Doc commandsbash scripts/verify-doc-commands.shTodo pnpm X documentado existe no package.json
A11y patternsbash scripts/validate-a11y-specs.shPadrões de acessibilidade presentes no código

Nível 1 — config + code ​

GateComandoO que verifica
Type checkpnpm typechecktsc --build — compilação TS inteira

Nível 2 — code only ​

GateComandoO que verifica
Error-code registrynpx tsx scripts/validate-error-codes.tsTodo HttpError usa código registrado (#634)

Pré-push (local — .husky/pre-push) ​

Roda a cada git push. Desde a big-fat-refactor-03 (Lane A) é o tier pesado local: além dos gates rápidos do pre-commit, roda a suíte unitária completa, a suíte de integração e a contagem de testes.

GateO que verifica
Tier code completobash scripts/quality-gates.sh push roda, primeiro, tudo que o pre-commit roda (gates rápidos + typecheck + error codes)
Test countbash scripts/verify-test-counts.sh — contagem de testes no baseline
Unit testspnpm -r test — Workers + App + Packages (~1766 testes)
Integraçãopnpm test:integration — vitest-pool-workers + D1 (21 arquivos, ~4 s)
Changelogbash scripts/validate-changelog.sh — tag v* tem entry no CHANGELOG
Doc drift mecânicoREADME/router.ts, README/permissões, configs referenciadas, version badge
Guarda online-only (#703)deploy-ijcp.yml e deploy-demo.yml mantêm VITE_ONLINE_ONLY=true + scripts/verify-online-build.sh; turbo.json repassa VITE_ONLINE_ONLY; package.json/app/src/config.ts mantêm build:online/marcador BUILD_MODE
Secret scanbash scripts/secret-scan.sh <base> — segredos adicionados no diff (diff-scoped)
Doc audit assíncronoreasonix em background (não bloqueia o push)

Por que o split (big-fat-refactor-03, Lane A). A suíte unitária inteira rodava a cada git commit, o que fazia do commit a ação mais lenta do repositório e desencorajava commits pequenos e frequentes. Ela passou a rodar no pre-push: continua na máquina do desenvolvedor e continua antes de qualquer coisa sair dela, mas uma vez por push em vez de uma vez por commit. O CI reexecuta as suítes a cada push/PR (unit-tests-ci, integration-tests), então nada se perde a jusante.

verify-test-counts.sh saiu do pre-commit e também ganhou um step no job core do CI: o pre-push é pulável com --no-verify, e sem o step no CI o ratchet viraria advisory por acidente.

A guarda #703 foi adicionada em 2026-08-21 (#685/#703): cobre os vetores exatos que reverteram o demo do v1.0.5 para o build legado.


CI (GitHub Actions — .github/workflows/ci.yml) ​

Roda em todo push para main e em todo pull_request. É o gate pesado autoritativo (#633): cada job chama diretamente os comandos dos gates do estágio ci do registry — o header do ci.yml mapeia job → gates, e a união das jobs cobre o estágio ci inteiro.

Exceção: o job e2e se auto-pula quando o diff é docs-only (CHANGELOG.md, README.md, DESIGN.md, CONTEXT*.md, docs/**, .specs/**) — fail-open: qualquer dúvida sobre o base ⇒ o job roda. Detalhes: docs/research/pre-push-doc-drift-audit-e2e-docs-only-2026.md.

JobGates do estágio ciO que verifica
securitysemgrepSemgrep (policy única: p/typescript + .semgrep/rules.yml)
coreaudit, lint, typecheck, unit-tests-ci, no-sql-in-routes, no-cloudflare-in-core, verify-test-counts, docs-lint, build-app, validate-csp, pre-deploy-checkBiome, tsc --build, testes unitários (paralelo entre workspaces), SQL em rotas, fronteira de portabilidade Cloudflare, ratchet de contagem de testes, markdownlint, dependências (advisory), build do app, CSP, pré-deploy
integrationintegration-testsTestes de integração (vitest-pool-workers, D1)
e2ee2e-smoke + step test:e2e:onlinePlaywright smoke (9 specs críticas, playwright.smoke.config.ts) + online (09/12/13/14, playwright.online.config.ts) — job auto-pulado em diffs docs-only
docsdocs-build, docs-links, docs-stalenessbuild do VitePress, dead links (lychee), staleness

Ordem preservada nos steps de cada job: o core roda build-app antes de validate-csp/pre-deploy-check (eles leem o app/public/_headers gerado pelo build); o docs roda docs-build antes de docs-links (ele crawleia docs/.vitepress/dist).

⚠️ O CI não passa --coverage ao pnpm -r test porque o @vitest/coverage-v8 é incompatível com o runtime workerd usado nos testes de integração do Workers.

Code Coverage ​

ℹ️ Gate informativo — badge + warning, não bloqueia merge (#643). O CI não roda cobertura (incompatibilidade do @vitest/coverage-v8 com o runtime workerd), então os thresholds só se aplicam localmente quando os testes rodam com VITEST_COVERAGE=1 (modo full do scripts/ci-local.sh).

Como rodar:

bash
VITEST_COVERAGE=1 pnpm -r test   # gera app/coverage + workers/coverage
pnpm coverage:check              # relatório informativo vs metas

scripts/check-coverage.sh (re-criado em #643) compara os relatórios gerados contra as metas de scripts/thresholds.sh e sempre sai 0 — avisa, nunca bloqueia. Também roda como DA-005 no bash scripts/doc-audit.sh. O badge de cobertura no README aponta para o Codecov (app.codecov.io/gh/barateza/neemias).

Metas (espelhadas nos vitest.config.ts — enforcement local; comparação do check-coverage.sh lê de scripts/thresholds.sh):

EscopoLinesBranches
app/ (global)2015
app/src/db/**8070
app/src/modules/auth/**2515
app/src/modules/sync/**4035
app/src/storage/**4535
workers/ (global)7065

⚠️ Metas acima da cobertura atual são intencionais (ex.: workers ~61% lines / ~57% branches, sync ~30% lines): pnpm coverage:check avisa até a cobertura alcançá-las. Quando alcançar, levantar as metas em scripts/thresholds.sh + vitest.config.ts (manter os dois em sincronia).


Módulos (modules/) ​

Os módulos vivem no próprio monorepo, em modules/packages/ (workspace aninhado com lockfile próprio — @neemias/modules, pnpm 12.3.1). Não existe mais um repositório separado (barateza/neemias-modules foi de-submoduled e arquivado). O CI instala o workspace de módulos separadamente (pnpm --dir modules install --frozen-lockfile --config.autoInstallPeers=true — o valor precisa bater com o autoInstallPeers gravado em modules/pnpm-lock.yaml) e valida os módulos com os mesmos gates do core: pnpm lint + pnpm typecheck + pnpm -r test.


Deploy ​

Há dois workflows de deploy no CI (além do ci.yml de qualidade):

  • .github/workflows/deploy-demo.yml — auto-deploy da demo (app.neemias.app / api.neemias.app) em todo push para main (ou workflow_dispatch).
  • .github/workflows/deploy-ijcp.yml — deploy da produção IJCP (ijcp.neemias.app / api-ijcp.neemias.app) por tag ijcp-v* (ou workflow_dispatch).

Ambos rodam no runner auto-hospedado: migrações D1 → worker → frontend (online-only, guardado por verify-online-build.sh — #703) → Pages → health. O deploy manual continua disponível como fallback:

  • Worker: pnpm deploy:worker (wrangler)
  • Docs: pnpm deploy:docs (Cloudflare Pages)
  • Tudo: bash scripts/deploy.sh (com VITE_ONLINE_ONLY=true + guarda para ENV=prod)

Tags v* são validadas localmente pelo pre-push (gate Changelog). Documentação detalhada: Deployment


Histórico ​

DataMudançaIssue
2026-09-11Pre-commit vira o tier rápido; suíte unitária + integração + contagem de testes passam para o pre-push; contagem de testes ganha step no job core do CI (o pre-push é pulável com --no-verify); novo gate no-cloudflare-in-core (ADR-0044) com ratchet CF_CORE_BASELINEbig-fat-refactor-03
2026-08-11Coverage vira gate informativo: check-coverage.sh recriado (compara vs metas em thresholds.sh, nunca bloqueia), thresholds por diretório no app/vitest.config.ts (db 80/70, auth 25/15, sync 40/35, storage 45/35), workers 70/65, badge Codecov no README, DA-005 no doc-audit.sh#643
2026-08-11RTM removido (scripts generate-rtm.ts, generate-rtm-from-code.sh, ci-validate-rtm.sh + artefatos gerados); drift i18n com tolerância de 5 commits (1→5); staleness 30→90 dias; doc-audit.sh reduzido a route count vs README; CI docs job perde rtm-freshness e docs-i18n-fixture#640
2026-08-11Baseline store baselines.sh + baselines.registry removidos; thresholds em scripts/thresholds.sh (4 constantes); spec-status.sh simplificado para listagem plana#639
2026-08-11CI chama comandos diretamente por job; runner scripts/gates.sh removido; gates.registry vira doc-only; pre-commit usa quality-gates.sh#638
2026-08-10CI vira o gate pesado autoritativo (lint/typecheck/unit/build/integração/E2E/docs via fatias do estágio ci); pre-push encolhe para changelog + doc-drift + secret scan; meta-gate de cobertura de CI no gates.sh verify#633
2026-08-09Quality-gates reconciliado com a realidade; meta-gate de wiring no gates.sh verify#634
2026-08-09error-code-registry, a11y-specs, index-completeness, doc-commands wired no pre-commit; rtm-freshness no CI#634
2026-08-09check-coverage.sh e mutation-matrix.sh retirados#634
2026-08-09Registry declarativo scripts/gates.registry + runner scripts/gates.sh#630
2026-08-09Baseline store scripts/baselines.registry + baselines.sh#631
2026-06-21Typecheck adicionado ao pre-commit#193
2026-06-21count-as-any adicionado ao pre-commit + CI#196
2026-06-19CI workflow inicial—

Distribuído sob licença MIT.