AIDive

Pack de vídeo

Skill, hook, subagent ou MCP: os 4 arquivos e o cartão de escolha

7 min de leitura

TL;DR e veredito

Quatro jobs que você vive digitando no Claude Code pertencem, cada um, a um arquivo diferente. Um checklist de release é uma skill. Uma regra que nunca pode quebrar é um hook PreToolUse. Uma auditoria que lê muitos arquivos é um subagent. Dados que o Claude não enxerga são um servidor MCP.

Rodamos os quatro em um repo real, uma API de faturas com 34 testes, e fizemos a escolha errada primeiro em todos. As escolhas erradas falharam na tela:

  • O checklist no CLAUDE.md acrescentou ao /context uma linha Memory files: 210 tokens, em cada turno.
  • O primeiro hook rodou, exibiu o aviso e a escrita passou (unitPrice de 8000 para 12000).
  • A auditoria rodada inline encheu a thread: Messages em 19.3k tokens, contra 7.2k com um subagent nomeado.
  • Sem um servidor de docs, o Claude respondeu "I couldn't find that setting... I won't guess one."

Veredito: escolha o tipo pela tabela da documentação e depois faça o arquivo responder, não apenas existir. Um hook que só registra é consultivo, como a linha do CLAUDE.md que ele substituiu. Dos quatro, só o hook nunca pergunta.

Copie os arquivos abaixo, faça o commit e comece pelo que você mais redigita.

Síntese com fontes

O que sua sessão carrega antes da sua primeira palavra. Nosso /context de linha de base mostrou 26.2k/1m tokens, sem linha de memória e sem linha de MCP. Cada job que você adiciona ou coloca uma linha nessa tabela ou não coloca. Leia-a primeiro e meça a partir dela.

Uma regra no CLAUDE.md é um pedido, um hook é imposição. A documentação diz que uma instrução como "never edit .env" no CLAUDE.md ou em uma skill é um pedido, e um hook PreToolUse que bloqueia a edição é imposição s1. A página Memory chama o CLAUDE.md de contexto, não de imposição s8. Uma thread no Hacker News relatou a mesma coisa do lado do hook: um hook que sai com 0 não bloqueia nada s10.

A tabela de gatilhos diz qual arquivo usar. A terceira colagem de um procedimento pede uma skill. Uma saída que inunda o chat pede um subagent. Uma aba que o Claude não enxerga pede um servidor MCP. Algo que precisa acontecer toda vez pede um hook s1.

Job 1, o checklist de release, é uma skill.

  • Um procedimento que cresceu dentro do CLAUDE.md pertence a uma skill, e o corpo dela só carrega quando é usada s3.
  • Na nossa rodada, o checklist colado no CLAUDE.md acrescentou uma linha Memory files: 210 tokens. Como skill com disable-model-invocation: true, o /context voltou à linha de base.
  • /release rodou seis passos (34 testes, 0.4.0 para 0.4.1, tag v0.4.1). Também notou que git commit -am ignora arquivos não rastreados, então colocou o novo changelog no stage primeiro.

Job 2, a regra que precisa valer, é um hook, e a garantia é a resposta que ele devolve.

  • Sair com 0 sem saída não é uma decisão. Sair com 2 bloqueia. O JSON de deny também bloqueia s2.
  • Nosso primeiro hook registrou, saiu com 0 e a escrita passou (unitPrice de 8000 para 12000). A resposta do Claude nunca mencionou o hook.
  • O mesmo hook devolvendo o JSON de deny recusou a edição com PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Em seguida o Claude perguntou: ignorar a proteção ou escrever a migração.
  • A documentação publica uma receita de PreToolUse para arquivos protegidos s23.
  • O limite: este hook vigia Edit e Write. Cubra o shell também, ou mantenha o shell fora da sua lista de permissões.
  • onFailure: "block" faz um hook com falha bloquear em vez de falhar aberto. Chegou na v2.1.295 s2 s15.

Job 3, a auditoria que inunda, é um subagent.

  • Um subagent começa às cegas e devolve um resumo s4.
  • Inline, Messages mostrou 19.3k. Com o subagent nomeado, mostrou 7.2k.
  • O arquivo do agente sozinho delegou em 0 de 2 rodadas. Citá-lo no prompt fez delegar. O arquivo faz o subagent existir, o seu prompt faz o Claude usá-lo.
  • Fixe o model: no arquivo. A conta não mudou: a leitura continua acontecendo, só que não na sua janela.

Job 4, dados que o Claude não alcança, é um servidor MCP.

  • claude mcp add --scope project grava .mcp.json, e a busca de ferramentas carrega só os nomes das ferramentas s5.
  • Existe um servidor HTTP gratuito e sem login para a documentação do próprio Claude Code, com search_claude_code_docs e query_docs_filesystem_claude_code_docs s24.
  • Sem ele, o Claude respondeu "I couldn't find that setting... I won't guess one." Com ele, o Claude respondeu "onFailure": "block", adicionado na v2.1.295, enquanto a nossa instalação era a 2.1.294.
  • Um servidor de docs para uma biblioteca que o modelo já conhece não mudou nada para nós. Conecte o que o modelo não pode ter.

Relatos de campo. Uma thread no Reddit relatou o Claude ignorando uma linha "MUST use agent" no CLAUDE.md s9. Citamos como um relato, não como uma medição. Os posts de engenharia da Anthropic tratam da mesma divisão s6 s7.

Código

Quatro arquivos da rodada, mais dois auxiliares. Os caminhos indicam onde cada um fica no seu repo.

1. A skill de release, em .claude/skills/release/SKILL.md. Digite /release. disable-model-invocation: true deixa a skill para você iniciar, que é o que se quer para algo que faz commit e tag.

SKILL.md

---
name: release
description: Cut a patch release of ledgerly (typecheck, tests, version bump, changelog line, commit, tag).
disable-model-invocation: true
---

Run these steps in order and stop at the first failure:

1. `pnpm typecheck` must exit 0.
2. `pnpm test` must pass.
3. Bump the patch version in `package.json` (e.g. 0.4.0 -> 0.4.1).
4. Add one line at the top of `CHANGELOG.md` (create it if missing): `## vX.Y.Z - YYYY-MM-DD` followed by a one-line summary of the commits since the last tag.
5. `git commit -am "release vX.Y.Z"`
6. `git tag vX.Y.Z`. Never push.

SKILL.md on GitHub

2. O hook que protege os dados, em .claude/settings.json. Ele devolve a decisão de deny com um motivo que o Claude lê. Precisa do jq no seu PATH e cabe inline, então o hook não precisa de um arquivo de script próprio. Troque o padrão data/.*\.json$ e o motivo pela sua regra.

settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
          }
        ]
      }
    ]
  }
}

settings.json on GitHub

3. O auditor de convenções, em .claude/agents/conventions-auditor.md. As ferramentas são limitadas às de leitura e o modelo é fixado. Edite a lista de convenções para combinar com o seu README.

conventions-auditor.md

---
name: conventions-auditor
description: Use when asked to audit the code against the README conventions
model: sonnet
tools: Read, Grep, Glob
---

You audit ledgerly's source against README § Conventions:

- Money is always an integer number of cents. Never floats.
- Domain functions return new objects; they never mutate their input.
- Validation failures throw `ValidationError` (400), missing records `NotFoundError` (404), illegal state transitions `ConflictError` (409).
- A change of stored shape bumps `schemaVersion` and adds a migration in `src/store/migrations.ts`.

Read every file under `src/`. Return only the findings: one line per violation as `file:line - convention - what is wrong`, then borderline cases the same way, then one line with the count of files read. No file contents, no summary of clean files.

conventions-auditor.md on GitHub

4. A config do MCP, salva como .mcp.json na raiz do repo. É o que claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp grava. O Claude Code lista um servidor de projeto como pendente até você aprová-lo na sua próxima sessão.

mcp.json

{
  "mcpServers": {
    "claude-docs": {
      "type": "http",
      "url": "https://code.claude.com/docs/mcp"
    }
  }
}

mcp.json on GitHub

Auxiliares. ctx.sh roda o /context em uma sessão headless e imprime as linhas de tokens, para você comparar antes e depois. mcp-probe.sh envia initialize e tools/list para uma url MCP HTTP, para conferir se um servidor responde antes de você adicioná-lo.

ctx.sh

#!/usr/bin/env bash
# Usage: ctx.sh <step>  -> runs "/context" through run-session.sh, writes context-<step>.txt (honours STRICT_MCP / EXTRA_ALLOW)
C="$(cd "$(dirname "$0")" && pwd)"
"$C/run-session.sh" "$C/out/context-$1.json" "/context" >/dev/null
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['result'])" "$C/out/context-$1.json" > "$C/context-$1.txt"
grep -E '^\*\*Tokens|^\| (System prompt|System tools|MCP tools|Memory files|Skills|Custom agents|Messages) ' "$C/context-$1.txt"

ctx.sh on GitHub

mcp-probe.sh

#!/usr/bin/env bash
# Usage: mcp-probe.sh <url> [tools/call json params]  -> initialize + notifications/initialized + tools/list (+ optional tools/call) over streamable HTTP
U="$1"; H=$(mktemp)
curl -s -D "$H" -m 30 -X POST "$U" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
echo; SID=$(grep -i '^mcp-session-id:' "$H" | tr -d '\r' | cut -d' ' -f2); echo "session-id: ${SID:-none}"
HS=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream'); [ -n "$SID" ] && HS+=(-H "Mcp-Session-Id: $SID")
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; echo
[ -n "${2:-}" ] && { curl -s -m 90 -X POST "$U" "${HS[@]}" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":$2}"; echo; }
rm -f "$H"

mcp-probe.sh on GitHub

Faça isto na segunda-feira

  • Abra uma sessão nova e rode o /context. Anote o total. Essa é a sua linha de base.
  • Escolha o checklist que você mais cola. Mova-o para .claude/skills/<name>/SKILL.md, adicione disable-model-invocation: true se ele faz commit ou tag, e rode-o com /<name>.
  • Pegue a regra que nunca pode quebrar e coloque-a em um hook PreToolUse que devolve permissionDecision: "deny" ou sai com 2. Peça ao Claude para quebrar a regra e confira se a edição é recusada.
  • Se o seu hook só registra, mude-o. Um hook que sai com 0 não bloqueia nada.
  • Mova uma tarefa que lê muitos arquivos para .claude/agents/<name>.md com um model: fixado. Cite-o no prompt até o Claude escolhê-lo sozinho.
  • Confira um fato que o Claude chutou para você recentemente. Se for mais novo que o modelo, adicione um servidor de docs com claude mcp add --transport http <name> --scope project <url> e faça o commit do .mcp.json.
  • Confira a versão do seu Claude Code antes de depender de uma configuração. onFailure: "block" exige a 2.1.295.
  • Rode git add -A && git status e faça o commit dos novos arquivos. Na nossa rodada apareceram exatamente quatro.

Para ir além

O cartão de escolha. Uma página. São as linhas da documentação s1 com os nossos quatro jobs escritos nelas. O destaque deste pacote são os quatro arquivos acima mais este cartão.

Tipo O que faz Use quando Nosso job Nosso arquivo
CLAUDE.md Contexto persistente carregado em toda conversa Uma convenção que o Claude erra duas vezes Uma linha, não um procedimento nenhum
Skill Instruções e fluxos que o Claude pode usar Você cola o mesmo roteiro pela terceira vez O checklist de release .claude/skills/release/SKILL.md
Hook Um script disparado por eventos Você quer que aconteça toda vez, sem perguntar Nunca editar dados armazenados à mão .claude/settings.json
Subagent Contexto isolado que devolve resultados resumidos Uma tarefa lateral inunda a sua conversa A auditoria de convenções .claude/agents/conventions-auditor.md
MCP Conecta a serviços externos Você vive copiando dados de uma aba que o Claude não enxerga A documentação do Claude Code .mcp.json
Plugin Reúne os quatro em uma unidade instalável Um segundo repositório precisa da mesma configuração Não usado nenhum

O cartão decide o tipo, não a qualidade. Um hook que só registra, um subagent que você nunca cita e um servidor para docs que o modelo conhece estavam todos no tipo certo e falharam. Escolha o tipo e depois faça o arquivo responder.

Quando não recorrer a estes.

  • Uma convenção que o Claude insiste em errar fica no CLAUDE.md como uma linha, não como um procedimento.
  • Um job que você conduz de um lado para o outro, com o seu histórico à vista, fica na thread principal, não em um subagent.
  • Um servidor de docs para uma biblioteca que o modelo já conhece custa contexto e não muda nada.
  • pnpm test é uma CLI que você já tem. Não é trabalho para MCP.

Plugins. Um plugin é a caixa que entrega os quatro juntos. Recorra a um quando outro repo precisar da mesma configuração.

Skill mais MCP. A documentação os une: o servidor fornece a conexão, e a skill ensina o Claude a usá-la bem.

Skill com context: fork. Uma skill pode rodar em um subagent em fork, para que a leitura dela fique fora da sua janela. Teste em um job antes de depender disso.

Tipos de hook além de command. Command é o que usamos. A referência de hooks lista outros tipos de handler, incluindo hooks http e prompt. A mesma regra vale: o que o hook devolve decide, e um hook silencioso não decide nada.

MCP contra CLI. Há um debate aberto sobre servidores MCP devorando contexto, com um post defendendo a CLI primeiro s11 e outro sobre o custo de contexto dos servidores MCP s12. Nossa regra: se uma CLI já resolve, use-a. Use MCP para o que não tem uma. A busca de ferramentas agora carrega só os nomes, então meça com o /context na sua própria configuração antes de decidir s5.

Mantenha as skills curtas e verificadas. Uma skill curta por job, conferida contra uma rodada real. Veja o paper SkillsBench s13 e a visão de um praticante sobre por que as skills custam pouco até serem usadas s14.

FAQ

Por que meu hook rodou mas a edição passou mesmo assim? Um hook decide só pelo que o comando dele devolve. Sair com 0 sem saída não é uma decisão s2. Devolva o JSON de deny, ou saia com 2.

Por que o Claude lê os arquivos ele mesmo quando eu tenho um subagent? Na nossa rodada, o arquivo do agente sozinho delegou em 0 de 2 rodadas. Cite-o no prompt, por exemplo "use the conventions auditor subagent".

O hook cobre tudo o que o Claude pode fazer? Não. O nosso casa com Edit e Write. O Claude se ofereceu para fazer a mudança pelo shell em uma rodada posterior, então cubra o shell ou mantenha-o fora da sua lista de permissões.

Devo adicionar um servidor MCP para cada biblioteca que uso? Não. Se o modelo já conhece a biblioteca, um servidor não muda nada e custa contexto. Adicione um para dados mais novos que o modelo ou fora do seu disco, e confira a versão instalada contra a que adicionou o recurso.

Posso fazer o commit de tudo isso? Sim. A skill, o arquivo de configurações, o arquivo do agente e o .mcp.json ficam todos no repo, então quem o clonar recebe tudo. Aprove o servidor MCP do projeto uma vez na sua próxima sessão.

Arquivos