smaug-padroes-gerais
Padrões gerais do monorepo Smaug — índice das demais skills, política de idioma, formatação, comentários, commits, branches, regras de dependência e comandos. Leia esta skill ANTES de qualquer tarefa
Files1
View on GitHubSKILL.md.claude/skills/smaug-padroes-gerais/SKILL.md
GitHubPadrões gerais do Smaug
Quando usar esta skill
Sempre, como primeiro passo. Ela diz qual skill específica abrir e fixa as regras que valem em todos os pacotes.
Índice — qual skill abrir
| Tarefa | Skill |
|---|---|
| Entender camadas do backend / adicionar um agregado novo | server-arquitetura |
Adapter de auth (argon2/jose/crypto), authConfig, env |
server-arquitetura |
| Escrever entidade ou value object | server-entidade-dominio |
| Criar/alterar erro de domínio | server-erros-dominio |
| Escrever caso de uso | server-caso-de-uso |
| Escrever porta + repositório Prisma | server-repositorio |
| Escrever service da aplicação ou DTO/schema Zod | server-servico-dto |
| Escrever controller, rota ou middleware (authenticate) | server-http |
| Registrar dependências | server-di-container |
| Testes do server (unit/integração) | server-testes |
| Entender camadas do frontend / criar página | web-arquitetura |
| Escrever hook de feature (TanStack Query) | web-feature-hooks |
| Escrever service de feature (axios) | web-feature-services |
| Escrever componente de feature ou formulário | web-componentes |
| Escrever primitivo de UI, hook ou lib compartilhada | web-shared-ui |
| Mexer em api-client, renovação, api-error, sessão | web-infra |
| Testes do web (Vitest + RTL + MSW) | web-testes |
| Testes E2E (Playwright) | testes-e2e |
Monorepo
server/, web/ e e2e/ são pacotes npm independentes (não é npm workspaces): cada um tem
package.json, lockfile e node_modules próprios — mais o pacote da raiz, com o tooling
compartilhado. São quatro lockfiles.
npm run install:all # obrigatório uma vez: raiz + server + web + e2e
npm run dev # server (3000) + web (3001)
npm run test # server e depois web
npm run lint # eslint (server) + eslint 9 flat config (web)
npm run format # prettier em todo o repo
npm run validate:deps # regras de arquitetura — SÓ da raiz
npm run test:e2e # Playwright, com SQLite exclusivo do run
node e2e/scripts/run-e2e.mjs --grep "<título>" # E2E com flags do Playwright (ver abaixo)
npm run devprecisa deserver/.envcom os segredos de autenticação (JWT_SECRET,JWT_KEY_ID,PASSWORD_PEPPERS,PASSWORD_PEPPER_CURRENT_ID— modelo emserver/.env.example) e deweb/.env.localcomAPI_ORIGIN=http://localhost:3000(modelo emweb/.env.example): o navegador só fala com o web, e o repasseweb/app/api/[...path]/route.tslêAPI_ORIGINa cada requisição. Não existe maisNEXT_PUBLIC_*da API inlined no build.npm run test:e2e -- --grep xnão repassa a flag (onpm run --prefix e2e testdo meio a consome); só caminho posicional passa (npm run test:e2e -- tests/smoke.spec.ts). Para--grep,--project,--headedetc. chame o wrapper direto:node e2e/scripts/run-e2e.mjs ….npm run --prefix server validate:depsestá quebrado (aponta para um.dependency-cruiser.cjsinexistente). Use sempre o script da raiz.web/instala estrito (npm ci/npm installsem flags), como os demais pacotes; com o React 19 estável não há mais.npmrcnem flag de peers.Prisma sempre pelos scripts do server (
prisma:generate,migrate:deploy,migrate:new,prisma -- <args>): eles escolhemprisma/<provider>/pelaDATABASE_PROVIDER(ambiente, depoisserver/.env), gravam o schema derivado e passam--config prisma/prisma.config.ts(a config do Prisma 7, ao lado dos schemas derivados, que aponta schema, migrations e URLs do provider) — e nunca alteram arquivo versionado.npx prismadireto não acha a config e falha. Mudança de schema = editarserver/prisma/schema.prisma+migrate:new, que cria a migration em SQLite e PostgreSQL.
Política de idioma
Regra observada em todo o código, sem exceção relevante:
| O quê | Idioma |
|---|---|
| Identificadores (classes, funções, variáveis, tipos) | inglês |
| Payloads da API, nomes de campo, endpoints, códigos de erro | inglês |
| Nomes de pasta de feature e de rota do Next | português (features/despesas, app/(app)/receitas) |
Texto de UI, mensagens de toast, aria-label |
português |
| Comentários de código e JSDoc | português |
| Títulos de teste do server | inglês — it("should … when …") |
| Títulos de teste do web e do e2e | português, minúsculo, verbo na 3ª pessoa |
Specs (specs/), README, mensagens de commit |
português |
Formatação e lint
Prettier é a única lei de estilo (.prettierrc, sem override por pacote):
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always"
}
O eslint.config.js da raiz (aplica a server/ e e2e/) tem rules: {} — só
js.configs.recommended + tseslint.configs.recommended. O web/ usa ESLint 9 com
configuração flat (web/eslint.config.mjs, script eslint .): eslint-config-next/core-web-vitals
eslint-config-next/typescript, também sem regra própria, exceto liberarno-explicit-anyeno-non-null-assertionem arquivos de teste (override no mesmo arquivo).
Consequência prática: any e ! são proibidos em código de produção do web/ e permitidos em
teste. No server/, req.userId! é a exceção deliberada (ver server-http).
Husky + lint-staged rodam o .lintstagedrc.js da raiz no commit. O bloco lint-staged dentro
de server/package.json é código morto — nada o invoca.
Comentários — a regra da casa
Comentário não descreve o que o código faz; ele registra o porquê, nomeando a falha que foi observada de verdade e a alternativa que foi descartada. Exemplares a imitar:
web/shared/lib/money.ts(parseMoneyInput) — cita o bug doparseFloat(v.replace(",", "."))que virava1.234.web/infra/api-client.ts— o interceptor de 401 explica por que só renova quem saiu com o token atual ("renovar de novo giraria o refresh token à toa — o que dá 409 na outra aba") e que "Rede ou 5xx são transitórios".web/vitest.config.ts— justifica o threshold relaxado deapp/**e lista as duas alternativas rejeitadas.e2e/src/support/ui.ts—waitForHydrationdescreve a corrida real (submit nativo virandoGET /login?email=…).
Não escreva comentário que repete a linha seguinte. Código morto ou comentado não entra (princípio II da constituição).
Regras de dependência (.dependency-cruiser.js, raiz)
Sete regras error e uma warn, nesta ordem no arquivo:
no-server-to-web—server/não importaweb/.no-web-to-server—web/não importaserver/.no-domain-to-infra—server/src/domainnão importainfrastructure/presentation.no-application-to-infra—server/src/applicationnão importainfrastructure/presentation.auth-crypto-only-in-infra—argon2ejosesó são importados emserver/src/infrastructure/(ficam atrás das portasPasswordHasher/AccessTokenService).http-middleware-libs-only-in-presentation—cookie-parsereexpress-rate-limitsó emserver/src/presentation/.no-e2e-to-src—e2e/não importaserver/nemweb/(é caixa-preta; fala só HTTP).no-circular-dependencies— apenas aviso.
As regras 5 e 6 são o modelo para dependência de runtime com dono: biblioteca nova que deve viver
numa camada só ganha uma regra from: { path: "^server/src/", pathNot: "^server/src/<camada>/" },
to: { path: "node_modules/(<lib>)/" }, copiada de uma delas.
{
name: "auth-crypto-only-in-infra",
comment:
"argon2 e jose ficam atrás das portas PasswordHasher/AccessTokenService: só os adapters de server/src/infrastructure os importam",
severity: "error",
from: { path: "^server/src/", pathNot: "^server/src/infrastructure/" },
to: { path: "node_modules/(argon2|jose)/" },
},
Rode npm run validate:deps (da raiz) antes de abrir PR.
Furo conhecido (pré-existente): a config não tem options.tsConfig, então o dependency-cruiser
não resolve o alias @src/* — import … from "@src/infrastructure/…" dentro de domain/ passa
calado pelas regras 3 e 4. Elas só pegam import relativo. As regras 5 e 6 funcionam, porque miram
node_modules/. Até o furo ser fechado, a revisão é quem garante a direção das camadas quando o
import usa o alias.
Git
- Conventional Commits:
type(escopo): assunto. Tipos usados:feat,fix,refactor,docs,chore,test. - Escopo é opcional e vem em dois sabores: id da feature (
feat(002-receitas),docs(003-despesas)) ou pacote/área (chore(server),refactor(web),chore(tooling)). - Assunto no imperativo; o histórico mistura pt-BR e inglês — prefira pt-BR em código novo.
- Corpo: linha em branco, depois lista de bullets
-quebrada em ~72 colunas. - Trailer estabelecido:
Co-Authored-By: Claude <modelo> <noreply@anthropic.com>. - Branches:
###-feature-name(005-implementacao-web), merge por PR.
Spec Kit
Trabalho de feature vive em specs/###-feature-name/ (spec.md, plan.md, research.md,
data-model.md, tasks.md, contracts/, checklists/) e é dirigido pelas skills speckit.*
apoiadas em .specify/.
.specify/memory/constitution.mdé normativo e vence a prática ad-hoc. Usa modais RFC-2119 em maiúsculas e português (DEVE / NÃO DEVEM / PROIBIDAS), com um**Rationale**:fechando cada princípio.- Todo
plan.mdcarrega um Constitution Check com o tripé por princípio:- **Status**: PASS/- **Evidence**: …/- **Compliance**: …. tasks.mdusa- [ ] T### [P] [US#] descrição com o caminho exato do arquivo; fases numeradas,**Checkpoint**:ao fim de cada fase,🎯 MVPnas histórias P1.
Como refatorar sem quebrar o padrão
- Antes de mudar, abra a skill do elemento e leia a seção Inconsistências conhecidas.
- Quando o código tem dois jeitos de fazer a mesma coisa, siga o lado indicado pela skill e nunca introduza um terceiro.
- Refatorar só o que a tarefa exige; YAGNI é mandatório (princípio V).
- Alteração de contrato da API sempre em par: código de erro novo no
serverentra também na tabelaMESSAGESdeweb/infra/api-error.tsno mesmo PR. - Cobertura é gate:
serverexige 100/100/100/100;webtem thresholds por glob. Uma refatoração que derruba cobertura não passa no CI. docs/prototipo/é referência visual, não código de produção — não copie padrão de lá (roda em React UMD e persiste emlocalStorage).