A Filosofia do Build Incremental
Por que um prompt de cada vez muda tudo
A abordagem mais comum para construir sistemas complexos com Claude é despejar uma especificação enorme em um único prompt e torcer para que tudo funcione. O Claudex foi construído de forma radicalmente diferente: sete prompts incrementais, cada um verificado antes do próximo. Essa não é uma escolha de estilo — é uma exigência das garantias de sistema que o Claudex precisa oferecer.
Cada layer não verificada é onde um bug se esconde
Quando você empilha código não testado sobre código não testado, a falha final pode ter origem em qualquer camada. Builds incrementais criam "fronteiras de verificação" — você só avança quando a camada atual está funcionando. No Claudex: os state files precisam funcionar antes de você ligar o hook neles. O hook precisa falhar aberto antes de escrever mensagens de BLOCK para o usuário.
Fazer
- ✓Verificar cada prompt antes de continuar
- ✓Ter o SPEC.md aberto como referência constante
- ✓Testar isoladamente cada nova camada
- ✓Construir o estado antes de construir o comportamento
Evitar
- ✗Passar toda a spec em um único prompt gigante
- ✗Avançar sem confirmar que o passo anterior funciona
- ✗Misturar responsabilidades em um único passo
- ✗Depurar múltiplas camadas simultaneamente
O SPEC.md: O Blueprint Antes do Código
Arquitetura em texto como referência viva
Antes de um único arquivo de código ser escrito, o Claudex tinha um SPEC.md completo. Não um documento de requisitos corporativo — um blueprint técnico de página única que Claude pode consultar a qualquer momento durante o build. É a diferença entre construir com um mapa e construir com esperança.
O SPEC.md é a fonte de verdade, não o código
O SPEC define: o que o sistema faz, as garantias hard que nunca podem ser violadas, os ciclos de vida completos de cada modo, a estrutura dos state files com cada campo e tipo, os canais de comunicação entre componentes, as primitivas de segurança e as variáveis de ambiente configuráveis. Tudo em uma página que cabe na janela de contexto.
Ciclo de Vida
drafting → reviewing → summarizing → done, com tabela por fase indicando quem age e o que o hook faz
State File
Cada campo definido: tipo, notas, valores possíveis. review_id com regex. review_id como padrão YYYYMMDD-HHMMSS-xxxxxx
File Tree
Estrutura completa de diretórios com cada arquivo nomeado e sua responsabilidade
Mantenha o SPEC.md aberto em outra aba enquanto faz o build. Cada prompt de construção começa com "Read SPEC.md if it's in this folder; that's the source of truth." Isso ancora Claude no blueprint mesmo quando o contexto da conversa cresce.
As 6 Garantias de Segurança do Sistema
Invariantes inegociáveis que o sistema nunca viola
O SPEC.md define seis primitivas de segurança que precisam ser verdadeiras em todo momento do ciclo de vida do Claudex. Elas não são features — são restrições do design. Violá-las significa que o sistema pode travar o usuário, corromper estado ou criar loops infinitos silenciosos.
ERR Trap Fail-Open
A primeira linha ativa do hook instala trap '... approve; exit 0' ERR. Qualquer erro não tratado retorna approve. O usuário NUNCA fica preso.
Escritas Atômicas de Estado
Toda mutação vai via tmp + rename. Arquivos nunca ficam parcialmente escritos. Um crash no meio de uma escrita não corrompe o estado.
CAS em Transições de Fase
Compare-And-Swap: claudex_phase_transition só escreve se a fase atual corresponde ao from esperado. Impede duas paths de avançar o mesmo loop simultaneamente.
Lockfile + Liveness de PID
O PID armazenado pode ser testado com kill -0. Detecta se o processo pai ainda está vivo.
Sweeper de Loops Stale
find -mmin +15 remove loops abandonados em cada invocação de start-loop.sh. Configurável via CLAUDEX_STALE_MINUTES.
Validação de cwd
O estado armazena repo_root. O hook falha aberto se pwd != repo_root. O hook pode ter sido disparado em um projeto diferente do onde o loop começou.
Essas seis garantias foram identificadas no SPEC antes do primeiro prompt ser escrito. Não foram descobertas durante o desenvolvimento. Sistemas de segurança precisam ser planejados como restrições, não adicionados como features posteriores.
Prompt 01 — Esqueleto do Plugin
Estrutura e fail-open hook — o primeiro passo verificável
O primeiro prompt não constrói lógica real. Ele constrói o esqueleto: os manifestos, os comandos placeholder e, mais importante, um hook que falha aberto por padrão. A meta é verificar que o plugin carrega no Claude Code antes de adicionar qualquer lógica substantiva.
- •Manifesto do marketplace em
.claude-plugin/marketplace.json - •Manifesto do plugin em
plugins/claudex/.claude-plugin/plugin.json - •6 arquivos de slash command com frontmatter YAML (placeholder)
- •
hooks.jsonregistrando o Stop hook com timeout 60s - •
stop-hook.shcom ERR trap fail-open e stub incondicional - •
tests/platform-validation.shcom assertions coloridas
O Prompt Real (Cole no Claude Code)
I want to build a Claude Code plugin called `claudex`. Read `SPEC.md`
if it's in this folder; that's the source of truth.
For this first pass, just scaffold the layout. No real logic yet.
I want to verify the plugin loads in Claude Code before we wire up
anything substantive.
Build:
1. The marketplace manifest at `.claude-plugin/marketplace.json` and
the plugin manifest at `plugins/claudex/.claude-plugin/plugin.json`.
Author name `promptadvisers`. Description matches the spec.
2. Six slash command files under `plugins/claudex/commands/`:
`plan.md`, `review.md`, `status.md`, `doctor.md`, `cancel.md`,
`rollback.md`. Each one for now is a placeholder that just `echo`s
"TODO" so I can confirm the commands surface in Claude Code's
slash-command list. Use proper YAML frontmatter (`description`,
`allowed-tools: Bash`).
3. `plugins/claudex/hooks/hooks.json` registering a `Stop` hook that
calls `${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh` with a 60s timeout
and a `statusMessage`.
4. `plugins/claudex/hooks/stop-hook.sh`. **Critical**: this is
fail-open everywhere from day one. Install an `ERR` trap that prints
`{"decision":"approve"}` on any unhandled error and exits 0. The
default body for now: read stdin, log "no logic yet", print approve.
The user must NEVER get trapped because the hook crashed.
5. `plugins/claudex/tests/platform-validation.sh`. A simple bash test
that asserts the hook is executable, returns valid JSON when fed `{}`
on stdin, and fail-opens on garbage input. Print colored ✓/✗ per
check. Exit non-zero on any failure.
Don't write any state-machine code yet. That's the next prompt. Just
the skeleton + the unconditional fail-open hook.
Once you're done, run `bash plugins/claudex/tests/platform-validation.sh`
and tell me what it says.
O teste de plataforma no final não é opcional. Antes de avançar para o Prompt 02, você precisa ver todas as assertions passando. "Parece certo" não é uma fronteira de verificação válida.
Por Que "Falhar Aberto" é Inegociável
Hooks nunca podem bloquear o trabalho do usuário
Um Stop hook que retorna BLOCK diz ao Claude Code: "não deixe o usuário encerrar este turno ainda". Se esse hook crashar silenciosamente, sem retornar nada, o Claude Code pode ficar esperando indefinidamente. A filosofia fail-open significa: em caso de dúvida, deixar passar. Um loop que termina cedo demais é ruim. Um usuário preso em um loop que nunca termina é inaceitável.
O ERR trap como primeira linha ativa
A primeira coisa que o stop-hook.sh faz é instalar um trap global de erro. Não na segunda linha. Não depois das importações. A primeira linha.
trap 'echo "{\"decision\":\"approve\"}" ; exit 0' ERR
- •O hook roda em cada fim de turno do Claude. Uma falha de script em qualquer sessão de trabalho normal travaria o usuário.
- •Dependências externas podem estar ausentes (
python3, um arquivo de estado corrompido). O fail-open absorve esses casos. - •Durante o desenvolvimento, bugs no hook são inevitáveis. Sem fail-open, cada bug bloquearia a sessão do desenvolvedor.
O que acontece quando o hook recebe entrada inesperada
Hook recebe JSON malformado no stdin
Qualquer comando shell falha → ERR trap dispara automaticamente
Trap imprime {"decision":"approve"} no stdout
Claude Code recebe approve, usuário nunca percebe o problema
Prompt 02 — A Máquina de Estados
Gerenciando sessões do loop com state files atômicos
O segundo prompt introduz o coração do Claudex: a máquina de estados. Cada loop de planejamento é rastreado em um arquivo .state YAML-ish isolado. O prompt constrói os helpers de baixo nível que todo o resto do sistema usa — e os testa em isolamento completo antes de qualquer hook lifecycle ser escrito.
YYYYMMDD-HHMMSS-xxxxxxO Prompt Real (Cole no Claude Code)
We need the state machine before we wire the hook lifecycle. Claudex
tracks each loop in a YAML-ish file under
`.claude/claudex/<review_id>.state`. Per-loop, never shared. See
`SPEC.md` for the full field list.
Build:
1. `plugins/claudex/scripts/state-helpers.sh`. Sourceable. Exposes:
- `claudex_new_review_id` — generates `YYYYMMDD-HHMMSS-XXXXXX`
(six lowercase hex chars at the end).
- `claudex_validate_review_id <id>` — strict regex check; returns 0/1.
- `claudex_state_write <file> <content>` — atomic. Write to
`${file}.tmp.$$` then `mv -f`. Never partial files.
- `claudex_state_read_field <file> <field>` — grep the YAML-ish value.
- `claudex_state_set_field <file> <field> <value>` — sed in place via
tmp + rename. Also bumps `last_updated_at` to now (UTC ISO 8601).
Don't recursively bump itself when you ARE setting `last_updated_at`.
- `claudex_phase_transition <file> <from_phase> <to_phase>` —
compare-and-swap. Only writes if current phase matches `from`.
Updates `last_updated_at` in the same write. Returns 0/1.
- `claudex_lock_write <file>` — writes `$$`.
- `claudex_lock_is_active <file>` — `kill -0` on the stored PID.
- `claudex_sweep_stale` — `find -mmin +$CLAUDEX_STALE_MINUTES`
(default 15) on `*.state` files; remove the state file plus its
`.lock`, `-runner.sh`, `-prompt.txt`, and per-review subfolder.
- `claudex_find_active_loop` — print path to the most-recent state
file (by mtime) or return 1 if none.
2. `plugins/claudex/scripts/start-loop.sh`. Called by `/claudex:plan`
and `/claudex:review`. Behavior:
- Parse flags before the topic: `--rounds N`, `--from-draft`,
`--skip-interview`, `--interviewed`. Reject `--rounds 0` and
non-integers with a clear error. Reject `--from-draft` if `PLAN.md`
is missing.
- Refuse to start if any existing state file has phase NOT in
`{done, cancelled, errored}`. Phase-based concurrency, NOT
file-presence.
- Sweep stale loops first (`claudex_sweep_stale`).
- Generate a `review_id`, write the initial state file atomically,
write a lockfile.
- Print initial instructions to stdout.
3. Extend `plugins/claudex/tests/platform-validation.sh` with sections
that exercise every helper: id generation, validation rejecting bad
formats including `../../../etc/passwd`, atomic write round-trip,
CAS valid + stale, sweeper, lockfile alive vs dead PID,
active-loop finder.
Hook lifecycle stays a fail-open stub for now. We're just verifying
the state machine in isolation.
Run `bash plugins/claudex/tests/platform-validation.sh` and tell me
what passes.
Note que o Prompt 02 explicitamente diz "Hook lifecycle stays a fail-open stub for now". A máquina de estados é testada completamente em isolamento. Isso é intencional: você não pode testar corretamente o lifecycle do hook se os helpers que ele usa ainda não foram verificados.
Escrita Atômica e CAS: Estado que Nunca Corrompe
Os dois padrões que tornam o estado confiável em qualquer condição
Dois padrões de engenharia clássicos aparecem repetidamente no Claudex: escrita atômica via tmp + rename e transições de fase via Compare-And-Swap. Juntos, eles garantem que o estado nunca fica em um estado intermediário inconsistente, mesmo que o processo seja interrompido no meio de uma operação.
Escrita Atômica (tmp + rename)
# Errado: escrita direta pode ser parcial
echo "$content" > "$state_file"
# Certo: tmp + rename é atômico no mesmo fs
tmp="${state_file}.tmp.$$"
echo "$content" > "$tmp"
mv -f "$tmp" "$state_file"
O mv no mesmo filesystem é uma operação atômica do kernel. Ou o arquivo antigo existe, ou o novo existe. Nunca um arquivo vazio ou parcialmente escrito.
CAS — Compare-And-Swap
# claudex_phase_transition <file> <from> <to>
# Só escreve se a fase atual == from_phase
current=$(claudex_state_read_field "$f" phase)
if [[ "$current" != "$from_phase" ]]; then
return 1 # outro processo já avançou
fi
# Escreve nova fase atomicamente
claudex_state_set_field "$f" phase "$to_phase"
Impede duas invocações do hook de avançar o mesmo loop simultaneamente. Sem CAS, dois BLOCKs concorrentes poderiam criar estados inconsistentes.
Claude Code pode disparar o Stop hook múltiplas vezes em rápida sucessão se o usuário encerrar o turno enquanto o hook ainda está processando. Sem CAS, dois disparos simultâneos do hook poderiam ambos ler phase=drafting, ambos escrever a transição para reviewing, e criar dois runners para a mesma rodada. Com CAS, o segundo disparo encontra phase=reviewing e retorna 1 sem escrever.
Esses dois padrões — tmp+rename e CAS — aparecem em sistemas de banco de dados, sistemas operacionais e sistemas distribuídos há décadas. Aplicá-los em shell script não é over-engineering: é reconhecer que o Claudex é um sistema concorrente com estado compartilhado que precisa das mesmas garantias.
Resumo do Módulo 2.1
O que você aprendeu
- ✓ Por que builds incrementais com fronteiras de verificação produzem sistemas mais confiáveis
- ✓ Como o SPEC.md funciona como blueprint e fonte de verdade durante o build
- ✓ As 6 primitivas de segurança que o sistema nunca viola
- ✓ O Prompt 01: esqueleto do plugin com fail-open hook desde o início
- ✓ Por que fail-open é inegociável em qualquer hook de sistema
- ✓ O Prompt 02: máquina de estados com helpers testados em isolamento
- ✓ Escrita atômica (tmp+rename) e CAS como garantias de consistência de estado
Próximos passos
No Módulo 2.2, você vai ver o Prompt 03 — onde o hook recebe seu lifecycle real, aprendendo a orquestrar as transições de fase, gerar runner scripts para o Codex e fazer o loop se tornar verdadeiramente autônomo.
Também verá o Prompt 04 (extração de findings para economia de tokens) e o Prompt 05 (fase de sumarização para que o loop não termine em silêncio).