@andreias.ai · por baixo do capô

CLAUDE.md é pedido. Hook é garantia.

Cada seção do CLAUDE.md que dá pra checar com um comando virou hook: Projeto → SessionStart, Comandos → Stop, Estilo → PostToolUse. O que sobra no CLAUDE.md é julgamento. Copie os cinco blocos, nos caminhos indicados, e adapte ao seu projeto.

01CLAUDE.md

Só o que não dá pra checar com comando: o que é o produto, o porquê das decisões, como pensar.

# Projeto <nome>

## Produto
- O que é: <uma frase sobre o que o sistema faz e para quem>.
- O que não é: <o que fica de fora de propósito>.

## Por que decidimos
- <decisão>: <motivo>. Ex.: monolito modular por domínio, porque o time é pequeno e o deploy é um só.
- <decisão>: <motivo>.

## Como pensar
- Na dúvida entre duas soluções, escolha a mais simples de desfazer.
- Mudança no caminho do dinheiro pede teste antes do código.
- Se uma regra daqui puder ser checada por um comando, ela vira hook em .claude/settings.json.

02.claude/settings.json

Liga os três hooks. $CLAUDE_PROJECT_DIR aponta para a raiz do projeto, de onde quer que o Claude esteja rodando.

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/contexto-git.mjs\"" }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/testes-antes-de-parar.mjs\"", "timeout": 300 }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/formata.mjs\"" }]
      }
    ]
  }
}

03.claude/hooks/contexto-git.mjs

SessionStart: o que ele imprime entra no contexto. Branch, git status e últimos commits do momento, no lugar do texto que envelhece.

// SessionStart: o que este script imprime entra no contexto da sessão.
// Substitui a seção "Projeto" escrita à mão, que envelhece em uma semana.
import { execSync } from "node:child_process";
const git = (c) => { try { return execSync(`git ${c}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); } catch { return ""; } };
const branch = git("branch --show-current");
if (!branch) process.exit(0); // fora de um repositório git
const status = git("status --short") || "(sem mudanças)";
const commits = git("log --oneline -5");
console.log(`Estado do repositório agora:\nbranch: ${branch}\n\ngit status:\n${status}\n\núltimos commits:\n${commits}`);

04.claude/hooks/testes-antes-de-parar.mjs

Stop: roda npm test quando o Claude tenta encerrar. Falhou, sai com código 2 e o erro volta pra ele. Confere o stop_hook_active pra não prender a sessão (e o Claude Code libera depois de 8 bloqueios seguidos).

// Stop: roda os testes quando o Claude tenta encerrar. Falhou, sai com código 2:
// o encerramento é barrado e o erro (stderr) volta para o Claude, que continua.
import { spawnSync } from "node:child_process";
let entrada = "";
process.stdin.on("data", (c) => (entrada += c));
process.stdin.on("end", () => {
  const dados = JSON.parse(entrada || "{}");
  // já está continuando por causa deste hook: deixa parar, para não prender a sessão num teste que nunca passa
  if (dados.stop_hook_active) process.exit(0);
  const r = spawnSync("npm test", { shell: true, encoding: "utf8", timeout: 280_000 });
  if (r.status === 0) process.exit(0);
  const saida = `${r.stdout ?? ""}\n${r.stderr ?? ""}`.trim().split("\n").slice(-40).join("\n");
  console.error(`Os testes falharam. Corrija antes de encerrar.\n\n${saida}`);
  process.exit(2);
});

05.claude/hooks/formata.mjs

PostToolUse com matcher Edit|Write: formata com o prettier do projeto todo arquivo que o Claude salva.

// PostToolUse (matcher Edit|Write): formata o arquivo que o Claude acabou de salvar.
// Substitui a seção "Estilo": formatação é trabalho de máquina, não pedido.
import { spawnSync } from "node:child_process";
let entrada = "";
process.stdin.on("data", (c) => (entrada += c));
process.stdin.on("end", () => {
  const arquivo = JSON.parse(entrada || "{}").tool_input?.file_path;
  if (!arquivo || !/\.(js|mjs|cjs|ts|tsx|jsx|json|css|md|html)$/.test(arquivo)) process.exit(0);
  // usa o prettier do projeto; se não houver, não faz nada
  spawnSync(`npx --no-install prettier --write "${arquivo}"`, { shell: true, stdio: "ignore" });
  process.exit(0);
});

06Confira no seu projeto

Marque o que já está no lugar.