Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/agents/cnab240-bradesco.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ bonito. Fora do escopo atual, use a skill [`issue-report`](../skills/issue-repor
PDF.
- **Fatiar arquivo `.REM` por Bash é o caminho certo.** Não é código do repositório. Escreva o
script no scratchpad da sessão, nunca dentro do repo.
- **`pnpm`, jamais `npm`** — hook recusa. Scripts utilitários em TypeScript (`node
--experimental-strip-types`), nunca Python.
- **`pnpm`, jamais `npm`** — hook recusa. Scripts utilitários em TypeScript, executados com
`node arquivo.ts` (sem flag), nunca Python.
- **Gate antes de fechar:** `pnpm run typecheck && format:check && lint && test`. Vermelho é
regressão a corrigir agora, tenha ou não vindo do seu diff — nunca `skip`, nunca hook desligado.
Se travar, use a skill [`ts-quality-checker`](../skills/ts-quality-checker/SKILL.md).
Expand Down
10 changes: 6 additions & 4 deletions .claude/agents/docker-compose-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ Agente especialista em **Docker / Docker Compose** para o `core-api`. Atua quand
## Constraints invariantes

- **Imagem base Node** (quando ativada a build de prod do app): `node:24-bookworm-slim` (ou `node:24-alpine` se aceitarmos musl trade-off — ADR pendente). Sem `latest`. Sem `node:24` sem o `-slim`/`-alpine`.
- **Multi-stage obrigatório** em prod: `deps` → `build` (se tiver compile step; hoje `--experimental-strip-types`, então build step opcional) → `runtime`.
- **`# syntax=docker/dockerfile:1`, nunca um minor fixo.** A doc oficial ([build/buildkit/frontend](https://docs.docker.com/build/buildkit/frontend/)) diz que `:1` acompanha a última `1.x.x` e que uma versão específica "needs to be updated manually to continue receiving bugfixes". Minor fixo não é imutável (recebe patches `x.y.z`) nem atualizado — este repositório ficou em `1.10` por 17 minors. O digest pin do ADR-0011 vale para a imagem **base**, que vai para o runtime; o frontend do `# syntax` é baixado em build e não entra na imagem.
- **Multi-stage obrigatório** em prod: `deps` → `build` (se tiver compile step; hoje o Node executa `.ts` direto, então build step opcional) → `runtime`.
- **`USER nonroot`** (UID >= 10000) no estágio final.
- **`.dockerignore` rico** — `node_modules`, `.git`, `dist`, `coverage`, `.env*`, `secrets/`, `*.log`, `.claude/`, `handbook/`.
- **`HEALTHCHECK` declarado** em serviços críticos (MySQL, MinIO).
Expand Down Expand Up @@ -166,7 +167,7 @@ secrets:
### `Dockerfile` (esqueleto para quando build de prod for ativada — hoje a CLI roda local)

```Dockerfile
# syntax=docker/dockerfile:1.7
# syntax=docker/dockerfile:1
ARG NODE_VERSION=24-bookworm-slim

# --- deps ------------------------------------------------------------------
Expand Down Expand Up @@ -198,10 +199,11 @@ COPY --chown=app:app package.json ./
USER app
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD node --experimental-strip-types --no-warnings -e "fetch('http://localhost:3000/health').then(r => r.ok ? 0 : process.exit(1)).catch(() => process.exit(1))"
CMD node -e "fetch('http://localhost:3000/health').then(r => r.ok ? 0 : process.exit(1)).catch(() => process.exit(1))"

# `tini` (PID 1 init) — opcional via flag init: true do compose, ou via ENTRYPOINT.
ENTRYPOINT ["node", "--experimental-strip-types", "--no-warnings"]
# Sem flag de runtime: o type stripping é default no Node 24.
ENTRYPOINT ["node"]
CMD ["src/server.ts"]
```

Expand Down
16 changes: 10 additions & 6 deletions .claude/agents/nodejs-runtime-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ description: >

# nodejs-runtime-expert

Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + `--experimental-strip-types`** para o `core-api`. Atua como engenheiro sênior do runtime — escolhe API nativa antes de dep externa, modela graceful shutdown, diagnostica erros de module resolution.
Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + type stripping nativo** (`node arquivo.ts`, sem flag) para o `core-api`. Atua como engenheiro sênior do runtime — escolhe API nativa antes de dep externa, modela graceful shutdown, diagnostica erros de module resolution.

> **Herda integralmente** o `CLAUDE.md` raiz, [ADR-0002](../../handbook/architecture/adr/0002-keep-nodejs-runtime.md) (Node como runtime), [ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md) (Node 24 + TS 6). Roteador único: [`contratos-orchestrator`](./contratos-orchestrator.md).

Expand All @@ -36,13 +36,17 @@ Agente especialista em **Node.js 24 LTS** com **ESM + NodeNext + `--experimental
| Tecnologia | Versão / Flag | Origem |
| :----------------------------- | :------------------------------------------------- | :------------------------------- |
| Node.js | `>=24.0.0` | `package.json#engines.node` |
| `@types/node` | `^22.10.0` | `package.json#devDependencies` |
| TS execution mode | `--experimental-strip-types --no-warnings` | scripts `test`, `test:integration`, `cli:contracts`, `secrets:setup` |
| `@types/node` | `^24.0.0` | `package.json#devDependencies` |
| TS execution mode | `node arquivo.ts` — **sem flag** | todos os scripts do `package.json` |
| Module system | ESM (`"type": "module"`) | `package.json#type` |
| `tsconfig.module` | `NodeNext` + `allowImportingTsExtensions` | `tsconfig.json` |
| Subpath imports | `#src/*` → `./src/*` | `package.json#imports` |

`--experimental-strip-types` está em "Active development" em Node 24 (não Stable). Toda mudança que dependa dele acima de "test/dev" tem que ser validada — registrar em ADR se virar dependência de prod.
O type stripping **não é mais experimental**: é o comportamento default do Node desde a 23.6 (backport na 22.18), e a prova verificável é `--no-strip-types` existir como negação no `node --help`. Produção depende dele — o `ENTRYPOINT` da imagem é `tini -- node src/server.ts`, sem flag.

⚠️ **Nunca escrever `--experimental-strip-types` nem `--no-warnings` num comando deste repositório.** As duas foram removidas em 06/10/2026 após medição: a primeira é redundante; a segunda **esconde** `DeprecationWarning`, que é como o Node anuncia o que quebra na próxima major. Cobrado por `tests/cleanup/node-flags-not-redundant.test.ts` em todos os pontos de invocação. O que exige flag é `enum`/`namespace` (`--experimental-transform-types`) — e o repositório não tem nenhum, por `erasableSyntaxOnly` no `tsconfig`.

⚠️ **`--enable-source-maps` é o contrário: ela FICA, e removê-la é regressão.** A primeira versão desta regra mandava tirá-la, com a justificativa de que nenhuma dep de produção publicava `.js.map`. Era falso por erro de medição — o `find` rodou **sem `-L`**, e `node_modules/<dep>` é symlink sob pnpm, então a varredura nunca desceu no alvo. Com `find -L`: `drizzle-orm` publica **444** `.js.map`, `fast-xml-parser` mais 4. Reproduzido: sem a flag, um erro dentro da lib reporta `drizzle-orm/utils.js:109`; com ela, `src/utils.ts:208`. O type stripping preserva a posição do código **deste** repositório — e é por isso que o raciocínio engana: a flag nunca serviu a ele, serve às deps que publicam mapa. **Ao medir qualquer coisa dentro de `node_modules` sob pnpm, use `find -L`.**

---

Expand Down Expand Up @@ -170,9 +174,9 @@ describe('Money.fromCents', () => {

Rodar suíte específica:
```bash
node --test --experimental-strip-types --no-warnings tests/modules/contracts/domain/shared/money.test.ts
node --test tests/modules/contracts/domain/shared/money.test.ts
# Por nome:
node --test --experimental-strip-types --no-warnings --test-name-pattern="fromCents" tests/.../money.test.ts
node --test --test-name-pattern="fromCents" tests/.../money.test.ts
```

### AsyncLocalStorage para correlação
Expand Down
13 changes: 6 additions & 7 deletions .claude/agents/typescript-language-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ description: >

# typescript-language-expert

Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescript/native-preview` — [ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md)) para o repositório `core-api`. Atua como engenheiro sênior do type system — modela tipos avançados, justifica narrowing, lê o handbook oficial antes de prescrever.
Agente especialista em **TypeScript 7** (compilador nativo em Go — [ADR-0067](../../handbook/architecture/adr/0067-typescript-7-side-by-side-supersedes-0009-language.md) D1, sem o side-by-side desde o [ADR-0072](../../handbook/architecture/adr/0072-oxc-lint-supersedes-inquiry-0029-and-0067-d2.md)) para o repositório `core-api`. Atua como engenheiro sênior do type system — modela tipos avançados, justifica narrowing, lê o handbook oficial antes de prescrever.

> **Herda integralmente** o `CLAUDE.md` raiz (especialmente §"Anti-padrões"). Toda mudança em código de produção fecha no gate: `typecheck` + `format:check` + `lint` + `test`.

Expand All @@ -33,9 +33,8 @@ Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescrip

| Pacote | Versão | Origem |
| :------------------------------ | :--------------------------- | :------------------------------ |
| `typescript` | `^6.0.0` | `package.json#devDependencies` |
| `@typescript/native-preview` | `7.0.0-dev.20260515.1` | `package.json#devDependencies` ([ADR-0009](../../handbook/architecture/adr/0009-node-24-typescript-6-with-7-roadmap.md) §"Plano de migração") |
| `typescript-eslint` | `^8.59.3` | typecheck-aware lint |
| `typescript` | `7.0.2` (exata) | `package.json#devDependencies` — exata porque os gates de AST usam a API `unstable/` |
| `oxlint` + `oxlint-tsgolint` | `1.86.0` + `7.0.2003` | lint type-aware (`.oxlintrc.json`) |

`tsconfig.json` aplica strict completo: `strict`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `noImplicitReturns`, `exactOptionalPropertyTypes`, `useUnknownInCatchVariables`, `isolatedModules`, `verbatimModuleSyntax`, `allowImportingTsExtensions`, `module: NodeNext`.

Expand Down Expand Up @@ -80,7 +79,7 @@ Agente especialista em **TypeScript 6.0** (com roadmap para TS 7 via `@typescrip
- .claude/skills/ts-quality-checker/SKILL.md ← gate de qualidade
```

ADR-0009 é vinculante para qualquer decisão de runtime TS / TS 7 / `native-preview`.
ADR-0067 (D1, D3–D6) e ADR-0072 são vinculantes para qualquer decisão de compilador e de lint; o ADR-0009 segue vinculante no que eles não supersedem (runtime, `tsconfig` strict).

---

Expand Down Expand Up @@ -128,7 +127,7 @@ Re-leia o §"Regras invariantes de código" do `CLAUDE.md`. Resumo:
- **Branded types** para IDs e valores validados (smart constructor `(raw) => Result<Brand, BrandError>`).
- **Discriminated union + `switch` exaustivo** — usar `const _: never = x` no default em vez de `throw`.
- **`unknown` + narrowing** em vez de `any`. Se `as` for inevitável, comentar (`as unknown as T` em borda de adapter).
- **Return type explícito em funções exportadas** (regra do projeto via ESLint).
- **Return type explícito em funções exportadas** (regra do projeto via `explicit-function-return-type` no oxlint).
- **Subpath imports `#src/*`** em testes para evitar `../../../../`.

### Nunca
Expand Down Expand Up @@ -236,7 +235,7 @@ const REGISTRY = {
2. **Buscar a página do Handbook correspondente** em `handbook/reference/typescript/`.
3. **Propor 2 versões** — mínima (que resolve o problema) e ergonômica (que cria affordance para quem chama). Decidir com o usuário.
4. **Verificar com `pnpm typecheck`** — invariante.
5. **Verificar com ESLint** (`typescript-eslint` strict + type-checked).
5. **Verificar com `pnpm run lint`** (oxlint type-aware) e o gate `tests/cleanup/lint-gaps.test.ts` (casing e `class`).
6. **Documentar limitações** (perda de brand em transformação aninhada, etc.) em comentário no `.ts`.

---
Expand Down
2 changes: 1 addition & 1 deletion .claude/hooks/block-inline-interpreter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Substituições:
reescrever arquivo inteiro → Write
ler parte de arquivo → Read (offset/limit)
buscar padrão → Grep
transformar texto em lote → script .ts com node --experimental-strip-types
transformar texto em lote → script .ts executado com `node arquivo.ts`
(é o runtime do projeto — ver scripts/)

Se precisar mesmo de processamento pontual em shell, `jq`, `grep`, `sed` e `awk`
Expand Down
2 changes: 1 addition & 1 deletion .claude/hooks/pre-commit-tombstone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ if [ -z "${NODE_BIN}" ]; then
exit 1
fi

exec "${NODE_BIN}" --experimental-strip-types --no-warnings \
exec "${NODE_BIN}" \
"${REPO_ROOT}/scripts/handbook/tombstone.ts"
8 changes: 4 additions & 4 deletions .claude/hooks/pre-commit-typecheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ echo "▶ [2/4] Type check (tsc --noEmit)..." >&2
run_pnpm_script "typecheck" "typecheck"

# ---------------------------------------------------------------------------
# Check 3 — Lint (ESLint + typescript-eslint)
# Check 3 — Lint (oxlint, type-aware via oxlint-tsgolint)
# ---------------------------------------------------------------------------
echo "" >&2
echo "▶ [3/4] Lint (eslint)..." >&2
echo "▶ [3/4] Lint (oxlint)..." >&2
run_pnpm_script "lint" "lint"

# ---------------------------------------------------------------------------
# Check 4 — Tests (node:test + --experimental-strip-types)
# Check 4 — Tests (node:test; o stripping de tipos é default no Node 24, sem flag)
# ---------------------------------------------------------------------------
HAS_TESTS=$(find "${CORE_API_DIR}/tests" -name '*.test.ts' -print -quit 2>/dev/null || true)
if [ -n "${HAS_TESTS}" ]; then
echo "" >&2
echo "▶ [4/4] Test run (node --test --experimental-strip-types)..." >&2
echo "▶ [4/4] Test run (node --test)..." >&2
run_pnpm_script "test" "tests"
else
echo "" >&2
Expand Down
2 changes: 1 addition & 1 deletion .claude/hooks/regen-inquiry-index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ case "$file" in
esac

cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0
node --experimental-strip-types --no-warnings scripts/handbook/inquiry-index.ts >/dev/null 2>&1
node scripts/handbook/inquiry-index.ts >/dev/null 2>&1

exit 0
6 changes: 3 additions & 3 deletions .claude/hooks/stop-quality-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# - nenhum arquivo alterado → exit 0 imediato
# - só .md / .yaml / .sh / qualquer não-.ts → exit 0 (format já roda no PostToolUse)
# - QUALQUER .ts, em qualquer diretório → gate completo
# - tsconfig.json, eslint.config.js, package.json → gate completo
# - tsconfig.json, .oxlintrc.json, package.json → gate completo
# - .claude/rules/*.md → gate completo, apesar de ser .md: o bloco
# `verify:` dessas rules AFIRMA coisas sobre o código, e quem confere essas afirmações é
# `tests/cleanup/rules-self-verify.test.ts` — que só roda dentro de `pnpm test`. Sem esta
Expand Down Expand Up @@ -60,15 +60,15 @@ while IFS= read -r f; do
# `src/*.ts|tests/*.ts|…` que existiam aqui eram código morto, subsumidas por ele.
*.ts) needs_gate=1; break ;;
.claude/rules/*.md) needs_gate=1; break ;;
tsconfig.json | eslint.config.js | package.json) needs_gate=1; break ;;
tsconfig.json | .oxlintrc.json | package.json) needs_gate=1; break ;;
esac
done <<< "$changed"

log "arquivos alterados: ${n_changed}"

if [ "$needs_gate" -eq 0 ]; then
log "veredito: PULADO — nenhum alterado aciona o gate (sem .ts, tsconfig.json,"
log " eslint.config.js ou package.json). Formatação já rodou no PostToolUse."
log " .oxlintrc.json ou package.json). Formatação já rodou no PostToolUse."
log ""
log "alterados:"
printf '%s\n' "$changed" | sed 's/^/ /' >> "$LOGFILE"
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ verify:
- 'src/modules/financial/adapters/persistence/repos/document-repository.drizzle.ts'
---

Única camada que toca infra real (Drizzle, mysql2, S3, FS, processo externo). `ENUM` e JSON nativos são barrados por [`.semgrep/rules.yml`](../../.semgrep/rules.yml); `class` por ESLint; o prefixo de tabela por `tests/cleanup/table-prefix-isolation.test.ts`; o pool boot-scoped está em [`shared-persistence.md`](./shared-persistence.md). Nada disso se repete aqui.
Única camada que toca infra real (Drizzle, mysql2, S3, FS, processo externo). `ENUM` e JSON nativos são barrados por [`.semgrep/rules.yml`](../../.semgrep/rules.yml); `class` por `tests/cleanup/lint-gaps.test.ts`; o prefixo de tabela por `tests/cleanup/table-prefix-isolation.test.ts`; o pool boot-scoped está em [`shared-persistence.md`](./shared-persistence.md). Nada disso se repete aqui.

- **A borda do adapter converte exceção em `Result` — e é a única que pode.** `try/catch` é permitido aqui e em lugar nenhum acima: nem `Error`, nem exception, nem `null` cru sobem para application ou domain. Vale também para os mappers row↔domínio, que retornam `Result` porque o banco pode conter estado que o domínio rejeita. Uma exceção que vaza daqui destrói a propriedade que o `Result` existe para dar: erro visível na assinatura.

Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ verify:
expect: []
---

Application orquestra: não tem regra de negócio nem conhece infra. A regra de dependência (não importar `adapters/`) e a proibição de `interface` em port são cobradas por `tests/cleanup/application-depends-inward.test.ts`; `class` é barrado globalmente por ESLint. A atomicidade do `INSERT` na outbox vive em [`adapters.md`](./adapters.md), porque `appendOutboxInTx` é chamado em 12 arquivos de `adapters/` e em **nenhum** de `application/`.
Application orquestra: não tem regra de negócio nem conhece infra. A regra de dependência (não importar `adapters/`) e a proibição de `interface` em port são cobradas por `tests/cleanup/application-depends-inward.test.ts`; `class` é barrado globalmente por `tests/cleanup/lint-gaps.test.ts`. A atomicidade do `INSERT` na outbox vive em [`adapters.md`](./adapters.md), porque `appendOutboxInTx` é chamado em 12 arquivos de `adapters/` e em **nenhum** de `application/`.

- **Use case é factory function que recebe `Deps` e devolve `Result`.** `(deps: Readonly<{…}>) => (input) => Promise<Result<O, E>>` — 153 dos 157 use cases declaram um tipo `*Deps`, 151 devolvem `Promise<Result<…>>`. A sequência dentro é sempre a mesma: **validar → buscar → decidir no domínio → persistir → publicar evento**. O que faz esse formato valer não é a estética: é que `deps` ser argumento torna o teste uma chamada de função, sem container nem mock de módulo.

Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ verify:

Domínio puro: zero infra, zero framework, zero I/O. Abrange `src/modules/*/domain/` e o shared kernel (`src/shared/kernel/` — `Money`, `NonZeroMoney`, `Period`, `PlainDate`, `UserRef`, `Cpf`, `Cnpj`).

O que já é cobrado por mecanismo e **não** se repete aqui: `throw` (`tests/cleanup/domain-no-throw.test.ts`), `class`/`this` (ESLint `no-restricted-syntax`), `any` (`no-explicit-any`), `switch` exaustivo (`switch-exhaustiveness-check` + `noFallthroughCasesInSwitch`) e parâmetro mutável (`prefer-readonly-parameter-types`). As primitivas `Result`, `Brand`, `immutable` e `exhaustiveStringUnion` vivem em `src/shared/primitives/` e têm rule própria — [`shared-primitives.md`](./shared-primitives.md).
O que já é cobrado por mecanismo e **não** se repete aqui: `throw` (`tests/cleanup/domain-no-throw.test.ts`), `class` (`tests/cleanup/lint-gaps.test.ts`), `any` (`no-explicit-any`), `switch` exaustivo (`switch-exhaustiveness-check` + `noFallthroughCasesInSwitch`) e parâmetro mutável (`prefer-readonly-parameter-types`). As primitivas `Result`, `Brand`, `immutable` e `exhaustiveStringUnion` vivem em `src/shared/primitives/` e têm rule própria — [`shared-primitives.md`](./shared-primitives.md).

- **Erro de domínio é string literal union, nunca classe.** `type ContractError = 'contract-not-active' | 'contract-cannot-expire-yet' | …`. É o que faz o `E` do `Result` ser exaustivo no `switch` do chamador — e o que o compilador consegue cobrar. Uma classe de erro devolve a decisão para runtime e reintroduz hierarquia onde o projeto escolheu união fechada.

Expand Down
Loading
Loading