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.