📂 Estrutura de repositório para plugins Claude Code
Uma estrutura consistente de repositório acelera onboarding, evita arquivos perdidos e facilita manutenção. O padrão abaixo é testado em projetos reais e separa claramente configuração, hooks, skills e estado.
🗂️ Estrutura recomendada
meu-projeto/
├── .claude/
│ ├── settings.json # Hooks, comandos, permissões
│ ├── hooks/ # Scripts de hook
│ │ ├── validate-commit.sh
│ │ ├── audit-writes.sh
│ │ └── notify-stop.sh
│ ├── skills/ # Arquivos de skill (slash commands)
│ │ ├── revisar-pr.md
│ │ ├── gerar-componente.md
│ │ └── analisar-deps.md
│ ├── state/ # Estado persistente dos hooks
│ │ ├── .gitignore # Ignorar estado em git (opcional)
│ │ └── hook-stats.yaml
│ └── logs/ # Logs dos hooks (não commitar)
│ └── .gitignore # logs/* ignorado
├── CLAUDE.md # Instruções do projeto
├── SPEC.md # Especificação do sistema de hooks
└── src/ # Código do projeto em si
💡 .gitignore para logs e estado
Crie .claude/logs/.gitignore com * para ignorar todos os logs. Para estado, decida caso a caso: estado que precisa ser compartilhado com o time deve ser commitado; estado temporário de sessão não.
🧪 Como testar um hook sem quebrar o fluxo
O melhor teste de hook é passar o JSON de input manualmente via stdin, sem o Claude Code. Isso permite validar a lógica em isolamento total — sem latência de API, sem custo de tokens, 100% repetível.
🧪 Técnicas de teste
# Teste 1: Input inline via echo
echo '{"event":"PreToolUse","tool":{"name":"Bash","input":{"command":"git commit -m fix"}}}' \
| .claude/hooks/validate-commit.sh
echo "Exit code: $?"
# Teste 2: Input de arquivo fixture
cat .claude/tests/fixtures/bash-git-commit.json \
| .claude/hooks/validate-commit.sh
# Teste 3: Script de teste automatizado
# .claude/tests/test-validate-commit.sh
#!/bin/bash
PASS=0; FAIL=0
run_test() {
local name="$1"
local input="$2"
local expected="$3"
echo "$input" | .claude/hooks/validate-commit.sh > /dev/null 2>&1
local actual=$?
if [ "$actual" = "$expected" ]; then
echo "✓ $name"
PASS=$((PASS + 1))
else
echo "✗ $name (esperado $expected, obteve $actual)"
FAIL=$((FAIL + 1))
fi
}
run_test "mensagem curta deve bloquear" \
'{"tool":{"name":"Bash","input":{"command":"git commit -m '\''fix'\''"}}}' \
1
run_test "mensagem boa deve aprovar" \
'{"tool":{"name":"Bash","input":{"command":"git commit -m '\''feat: add user authentication'\''"}}}' \
0
echo "Resultado: $PASS passou, $FAIL falhou"
🪵 Logs e debugging
Hooks executam em silêncio — sem logs, você não tem visibilidade. Stdout é reservado para comunicação com o Claude; use stderr para erros e arquivo de log para rastreabilidade permanente.
🪵 Padrão de logging em hooks
#!/bin/bash
# Padrão de logging reutilizável
LOG_FILE="${CLAUDE_LOG_DIR:-.claude/logs}/validate-commit.log"
mkdir -p "$(dirname "$LOG_FILE")"
# Função de log com timestamp e nível
log() {
local level="$1"; shift
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] validate-commit: $*" >> "$LOG_FILE"
}
# Redireciona stderr para log E para o fd original
exec 3>&2 # Salva fd 2 original
exec 2> >(tee -a "$LOG_FILE" >&3) # Tee: stderr vai para log e original
log INFO "Hook iniciado — PID: $$"
# --- Lógica do hook ---
PAYLOAD=$(cat)
log DEBUG "Payload recebido: $(echo "$PAYLOAD" | head -c 200)"
if [ condição_de_bloqueio ]; then
log WARN "Bloqueando: motivo_aqui"
echo "Mensagem de erro para o Claude" >&2
exit 1
fi
log INFO "Aprovado"
exit 0
💡 Monitorar logs em tempo real
Durante desenvolvimento, mantenha um terminal com tail -f .claude/logs/validate-commit.log aberto. Cada execução do hook aparece em tempo real — você vê exatamente o que está acontecendo enquanto trabalha no Claude Code.
📋 O SPEC.md como bússola
O SPEC.md é o documento que captura decisões de design antes do código. Para hooks, ele define o que o hook deve fazer, quais invariantes mantém, quais são os edge cases e por que certas escolhas foram feitas.
📋 Template de SPEC.md para hook
# SPEC: validate-commit hook
## Objetivo
Bloquear commits do Claude Code com mensagens que não seguem
Conventional Commits ou são muito genéricas.
## Responsabilidade
- INTERCEPTA: PreToolUse Bash quando o comando contém "git commit"
- BLOQUEIA se: mensagem tem menos de 20 chars OU é genérica
- APROVA se: mensagem segue o padrão ou tem 20+ chars descritivos
## Invariantes
1. Falha silenciosa = aprovação (fail-open)
2. Nunca bloqueia commits de merge automático
3. Mensagem de erro é acionável (diz o que fazer, não só o que está errado)
## Casos de borda
- git commit --amend: APROVADO (não valida re-commit)
- git commit -m 'WIP: ...' : APROVADO (WIP é exceção explícita)
- jq não instalado: APROVADO (fail-open, log warning)
## Dependências
- jq (obrigatório, com fallback para grep)
- bash 4.0+
## Decisões de design
- Por que não usar regex mais complexo: manutenibilidade > precisão
- Por que não checar branch: escopo pequeno, fácil de expandir depois
🌿 Versionamento de prompts
Skills e CLAUDE.md são código — devem ser versionados no git com commits descritivos. Cada iteração de um prompt é um commit: você consegue ver o que mudou, por que mudou e fazer rollback quando algo quebra.
✓ Boas mensagens de commit de prompt
skill(revisar-pr): adiciona verificação de tipos TypeScriptclaude.md: instrui leitura de MEMORY.md ao iniciar sessãoskill(gerar-componente): corrige template de stories✗ Mensagens ruins
update promptsfix skillajustes no CLAUDE.md📊 Por que commitar prompts incrementalmente
- • Permite rollback quando uma nova instrução causa comportamento inesperado
- • Facilita code review: outra pessoa pode revisar mudanças no prompt
- • Cria histórico de "por que este prompt existe" nos logs do git
- • Permite comparar versões com
git diff HEAD~1 HEAD CLAUDE.md
🔧 Ferramentas de apoio: jq, yq, shellcheck
Três ferramentas eliminam as classes de erro mais comuns em desenvolvimento de hooks. jq, yq e shellcheck são o kit mínimo que todo desenvolvedor de hooks deve ter instalado.
Processa JSON no terminal. Essencial para parsear o payload de stdin dos hooks.
# Instalação
brew install jq # macOS
apt install jq # Ubuntu/Debian
# Uso em hook
COMMAND=$(echo "$PAYLOAD" | jq -r '.tool.input.command // empty')
TOOL=$(echo "$PAYLOAD" | jq -r '.tool.name // empty')
# Query condicional
echo "$PAYLOAD" | jq 'if .tool.name == "Bash" then .tool.input.command else "" end'
Processa YAML com a mesma sintaxe do jq. Indispensável para ler arquivos de estado em YAML.
# Instalação
brew install yq # macOS
snap install yq # Linux
# Ler campo simples
VERSION=$(yq '.version' state.yaml)
# Ler com default
RUNS=$(yq '.stats.runs // 0' state.yaml)
# Atualizar in-place
yq -i '.version += 1' state.yaml
Analisa scripts shell e detecta bugs comuns antes de você rodar em produção. Integre no CI.
# Instalação
brew install shellcheck # macOS
apt install shellcheck # Ubuntu/Debian
# Verificar um hook
shellcheck -S error .claude/hooks/validate-commit.sh
# Erros comuns que shellcheck detecta:
# - Variáveis não quotadas (word splitting)
# - Comparações sem quotes ($VAR vs "$VAR")
# - Uso de [ vs [[ inconsistente
# - Pipelines que mascaram erros (sem set -o pipefail)
✅ Resumo do Módulo 1.5
Próximo Módulo:
1.6 — Seu Primeiro Hook de Validação — projeto prático completo aplicando tudo da Trilha 1