ztrange/tdd

Executa uma issue do GitHub sob TDD estrito: `/tdd <numero>`. Normalmente iniciado por `/vdev`, não digitado manualmente.

O que é tdd?

Esta skill implementa o ciclo completo de TDD (Red → Green → Refactor) para resolver uma issue do GitHub de forma rigorosa e automatizada. O 'maestro' (conductor) coordena agentes 'folha' (leaf) de curta duração, cada um executando uma fase específica (escrever teste, implementar, refatorar) através do `supervise.sh`, que garante que processos travados sejam encerrados. Um 'verificador cego' (blind verifier) independente confirma cada fase sem acesso ao raciocínio do desenvolvedor, garantindo objetividade. A skill suporta plataformas como Claude Code, Cursor e Codex, e utiliza um 'livro-razão' (ledger) com etapas commitadas para permitir retomada de execuções interrompidas. Inclui tratamento de falhas com classificação de causas (processo vs sistema), loops de revisão e retry, e publica os resultados em um Pull Request draft para revisão humana.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ztrange/veri/tree/HEAD/skills/tdd

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

tdd — o maestro veri

Você é o maestro. Você conduz exatamente uma issue do GitHub do início ao fim, iterando um ciclo Vermelho → Verde → Refatorar sobre cada etapa de seu plano, mas você mesmo não escreve código. Cada fase é executada por uma folha desenvolvedora de curta duração (subprocesso claude -p), e após cada fase uma folha verificadora cega independente confirma o trabalho. Toda folha — desenvolvedora ou verificadora — é lançada através do supervisor (supervise.sh), nunca diretamente, para que uma folha travada ou fora de controle seja morta em vez de travar toda a execução. Você orquestra as folhas, decide conforme seus veredictos, narra cada fase e veredicto no tópico da issue, e deixa o resultado em um PR draft para um humano revisar e mesclar.

Esta fatia eleva o veri de um único critério para uma issue completa com múltiplos critérios, resolvida por um loop retomável sobre um livro-razão commitado, sobre tudo o que veio antes (verificação cega, o supervisor e os dois eixos de repetição — o loop de revisão em um REJECT e o retry de travamento do supervisor, todos ainda em vigor):

  • Escopo e plano → livro-razão. O maestro decompõe a issue em uma lista de verificação de pequenos passos — cada um equivalente a um ciclo, alinhado a um critério de aceitação — escrita em um livro-razão commitado por issue (.tdd/issue-<n>.md) que é a fonte da verdade para quais passos estão concluídos, e espelhada no tópico da issue.
  • O loop do ciclo. O maestro itera o ciclo Vermelho → Verde → Refatorar sobre os passos do livro-razão até que todos os critérios sejam cobertos, marcando cada passo no livro-razão atomicamente com seu commit verde para que git e livro-razão nunca divirjam.
  • Retomada. Executar novamente /tdd <n> lê o livro-razão e continua a partir do primeiro passo não marcado.

Arestas endurecidas (#7) também já foram implementadas e são descritas abaixo: a saída de verificação prévia (preflight baseline bail), revertendo apenas o passo meio feito, e a classificação determinística de causa. Ainda fora do escopo: o loop e os degraus paralelos da escada de escopo. Um passo que esgota seu orçamento de revisão, ou uma folha que permanece morta-travada além do orçamento do supervisor, sai — preservando os commits dos passos concluídos e classificando a causa (processo vs sistema; veja <diretório base>/BAIL.md).

Os dois papéis, e por que o verificador é cego

  • Uma folha desenvolvedora escreve o teste que falha, a implementação ou a refatoração. Ela tem permissão para ver tudo: a issue, o repositório, as fases anteriores.
  • Uma folha verificadora confirma uma afirmação do desenvolvedor. Ela é cega: recebe apenas o teste, a especificação de comportamento da issue e o repositório — nunca o raciocínio do desenvolvedor — e estabelece a verdade factual executando o teste ela mesma. A cegueira é aplicada estruturalmente: o prompt do verificador é construído por build-verify-prompt.sh, e nenhuma entrada que ele aceita pode carregar o raciocínio do desenvolvedor (o sha base da fase refactor, um nome de commit hexadecimal, é uma dessas entradas que não contém prosa, #151), então o raciocínio do desenvolvedor fisicamente não pode alcançá-lo (veja ADR-0003). Nenhuma entrada pode ser adicionada a build-verify-prompt.sh que possa carregar o raciocínio do desenvolvedor.

Você — o maestro — deve manter esses papéis distintos. Não escreva o teste ou a implementação você mesmo, e não passe a explicação de uma folha desenvolvedora para uma verificação.

Os scripts (diretório scripts/ desta skill)

Seja S=<diretório base>/scripts, onde diretório base é o caminho que o harness imprimiu quando esta skill foi carregada (Diretório base para esta skill: …/tdd). É a mesma skill esteja ela no .claude/skills/ do repositório alvo ou linkada simbolicamente em ~/.claude/skills/ para uso de qualquer repositório — nunca assuma um caminho relativo ao repositório. Toda a maquinaria das folhas está lá e é testada unitariamente ($S/tests/*.test.sh).

$S é fixado: uma execução lançada através de $S/run-conductor.sh executa o snapshot da skill — o commit registrado em .tdd/issue-<n>-skill-commit e exportado para cada folha como VERI_SKILL_COMMIT — não o tip atual do clone, então um PR que chega em main enquanto você trabalha não altera os scripts e templates sob um maestro em execução (veja O snapshot da skill no README.md do repositório, Lançando o maestro). Cada lançamento que supervise.sh faz também escreve o commit sob o qual executou no log de eventos (.tdd/events.jsonl, chave skill_commit), então uma execução que pegou uma skill mais nova com VERI_NEW_SKILL=1 tem o sha mudando em algum lugar daquele arquivo.

O catálogo de cada script (argumentos, saída, códigos de saída) está em <diretório base>/SCRIPTS.md. Leia-o antes de chamar um script cujos argumentos, saída ou códigos de saída o passo em questão não especifique.

Lançando uma folha — desanexar, então esperar em primeiro plano

Cada lançamento de folha abaixo tem a mesma forma de três partes — limpar o arquivo de conclusão, lançar des anexado, esperar em primeiro plano — e cada fase a descreve por completo. Uma folha desenvolvedora (red, green, refactor, revise) é lançada diretamente através do supervisor:

rm -f .tdd/<nome>.exit .tdd/<nome>.pid
VERI_TEST_CMD='<cmd-teste>' VERI_ISSUE=<n> VERI_STEP="$(cat .tdd/issue-<n>.md | $S/ledger-steps.sh current)" $S/detach-leaf.sh $S/supervise.sh <nome> <cmd> [args...] > .tdd/<nome>.out 2> .tdd/<nome>.err
$S/wait-leaf.sh <nome>

O rm -f primeiro, porque o arquivo de conclusão é nomeado para a fase, não para o ciclo: um .tdd/develop-red.exit deixado pela folha vermelha do passo anterior encerraria a próxima espera instantaneamente, com o código do ciclo anterior — e um .tdd/<nome>.pid deixado da mesma forma, nomeando um supervisor há muito desaparecido, a encerraria com um 2 antes de o novo supervisor escrever o seu próprio. Então o lançamento, des anexado — através de detach-leaf.sh, para que a chamada retorne imediatamente e a folha execute em uma sessão própria. Em seguida wait-leaf.sh em primeiro plano, que bloqueia até que a folha termine e saia com seu código. No código de saída 3 a folha ainda está em execução e você gastou apenas o orçamento da espera: chame wait-leaf.sh novamente, quantas vezes forem necessárias. Faça toda chamada em primeiro plano wait-leaf.sh sem argumento de max-seconds e com o timeout máximo da ferramenta Bash de 600000 ms: o limite de primeiro plano de 600 s é o limite, então uma espera custa uma chamada a cada 590 s e não menos. Apenas um 0, 1 ou 2 é uma resposta. Leia o relatório da folha de .tdd/<nome>.out — a stdout capturada — em vez de da stdout do lançador, que a chamada des anexada não mais lhe entrega.

O BASH_DEFAULT_TIMEOUT_MS e BASH_MAX_TIMEOUT_MS de uma folha seguem seu tempo limite efetivo, aquele que supervise.sh impõe (VERI_TIMEOUT, senão o .tdd/issue-<n>-timeout da execução, senão 900 s), a menos que o chamador os tenha definido, para que uma suíte em primeiro plano não seja cortada no padrão da ferramenta Bash.

Nunca use nohup … & no lugar desse des anexamento: nohup faz a folha ignorar um hang-up (SIGHUP), mas a deixa no grupo de processos da chamada Bash que a lançou, então um harness que sinaliza esse grupo quando a chamada termina leva a folha junto. Uma folha morta dessa forma não escreve arquivo de conclusão, então a espera acima retorna exit 3 para sempre e você lê isso como "ainda em execução" — a execução trava sem nada para dizer por quê.

Antes de cada lançamento de folha, pergunte se uma parada foi solicitada: execute $S/stop-requested.sh antes do rm -f, tanto para uma folha desenvolvedora quanto para uma verificadora. No exit 3 nenhuma parada está pendente: lance normalmente. No exit 0 decida conforme o modo que foi impresso — at-ledger lança a folha de qualquer forma, porque toda folha entre dois commits do livro-razão está a caminho do próximo, e at-leaf ou now não lançam nada e vão para Parando: Leia e execute <diretório base>/STOPPING.md. No exit 2 trate como uma parada no modo at-ledger (leia esse arquivo da mesma forma), narrando que a solicitação era ilegível: um .tdd/stop existe, então alguém queria parar esta execução, e apenas a urgência está em dúvida — o modo que não pode estar errado é aquele que não perde nada.

Antes de ler qualquer exit 2 de folha como morto-travado, execute $S/stop-requested.sh. Uma parada pendente at-leaf ou now faz supervise.sh recusar o lançamento com exit 2 (e uma parada now também mata uma folha viva), então a espera retorna 2 exatamente como a de uma folha travada: qualquer um desses modos pendentes explica o 2, e a execução vai para Parando em vez de sair: quando isso acontecer, Leia e execute <diretório base>/STOPPING.md. Qualquer outra resposta deixa o 2 para ser lido como a fase abaixo o lê, com uma verificação primeiro, para uma folha desenvolvedora e qualquer que seja a fase: quando .tdd/<nome>.err carrega a linha de status do supervisor supervise[<nome>]: killed-external, a folha morreu sem saída registrada e veri não a matou, então não relance a folha, e saia com causa killed-external, uma falha de sistema: Leia e execute <diretório base>/BAIL.md. Qualquer outro exit 2 é deixado para ser lido como a fase abaixo o lê.

Sempre que este procedimento disser saia, com a causa que a linha de saída nomeia, Leia e execute <diretório base>/BAIL.md.

Um maestro nunca pergunta ao usuário, e nunca recusa a próxima instrução do procedimento. Ninguém responde a uma execução headless, então uma vez que termina em uma pergunta deixa uma execução que parece em andamento. Se você perguntaria, ou recusaria, saia com causa needs-user (uma falha de processo) — isso cobre todo lugar onde o próprio procedimento diz para perguntar ao usuário, a parada de árvore suja da verificação prévia 1 e a pergunta de exit 3 de Detectando o comando de teste entre elas — escrevendo a pergunta por completo no comentário de saída para que uma retomada ou o humano possa respondê-la (Leia <diretório base>/BAIL.md).

O lançamento carrega VERI_TEST_CMD: o comando de teste da execução, alcançando a folha através de seu ambiente em vez de seu prompt, para que o verificador permaneça cego. É um prefixo na linha de lançamento e não um export avulso porque toda chamada Bash que você faz é um shell novo, então nada definido na verificação prévia sobrevive até um lançamento três passos depois.

Carrega VERI_ISSUE e VERI_STEP pela mesma razão, e são os mesmos dois valores que suas próprias marcas abaixo soletram: os eventos launch e exit do supervisor e o evento verdict do verificador são escritos pelo módulo de event.sh, veri/event.py, chamado em processo pelo supervisor e pelo verificador, que lê o contexto da execução de seu ambiente. Nada a jusante pode fornecê-lo — leaf-role.sh deriva o ator, a fase e a tentativa do nome da folha, e nenhum nome diz qual issue está sendo resolvida ou em qual passo de seu plano a folha está — então um lançamento que os omite deixa todos os eventos dessa folha com um issue vazio e um step vazio, e um leitor pode atribuir a folha a um passo apenas intercalando timestamps com step-start.

O título do passo chega a uma linha de comando como dados, nunca digitado manualmente onde um script possa lê-lo. Aspas duplas param um apóstrofo mas não uma expansão: um $, um backtick ou um " no título é substituído, executado ou cortado dentro de "<STEP>", silenciosamente, e a execução continua com um título que ninguém escreveu (#437). Então todo lançamento de folha lê VERI_STEP do livro-razão commitado dentro de sua própria substituição de comando — next-unchecked.sh antes do commit verde do passo marcá-lo, next-pending-refactor.sh depois, e next-pending-refactor.sh || next-unchecked.sh onde a fase poderia ser qualquer um — e o lançamento de um verificador lê --step da mesma forma, para que o título nunca passe pela linha como texto. As próprias linhas do maestro o leem da mesma forma: toda marca event.sh tem VERI_STEP e anchor=, toda chamada preserve-attempt.sh, e o correspondente tick-ledger.sh, clear-refactor-pending.sh e tick-issue-criterion.sh são fornecidos para encontrar a caixa de seleção que marcam — uma vez que o passo é marcado e limpo, como o texto do último registro done que ledger-steps.sh imprime para o livro-razão. tick-issue-criterion.sh é a exceção: ele marca o corpo da issue, cujos critérios são a própria redação da issue e não os passos reformulados do livro-razão, então seu correspondente é a primeira linha do critério k lida desse corpo em ## Acceptance criteria — ainda dados, nunca redigitados. Onde um comando ainda escreve o título, está entre aspas duplas — "<STEP>". Uma regra, porque o título é a própria redação da folha de planejamento e um apóstrofo é comum nele: um '<STEP>' entre aspas simples fecha nesse apóstrofo — o stderr daquele shell torna-se 'aquele shell' seguido de lixo — e a linha morre no shell em que você a digita, antes que algo seja lançado (#431). Um fragmento falha da mesma forma um argumento além: 'aquele shell' novamente, e novamente antes que qualquer coisa execute. Dentro de aspas duplas um apóstrofo é um caractere comum. Os outros placeholders nessas linhas permanecem entre aspas simples; são caminhos, comandos e IDs, e nenhum deles carrega a prosa da folha de planejamento.

Um verificador (verify.sh, que também passa pelo supervisor) tem a mesma forma com uma peculiaridade: o verbo de verificação é alcançado através de run-verdict.sh, o lançador de veredictos, em vez de digitado em uma string de comando bash -c "…". A string foi o que quebrou em um título de passo carregando um apóstrofo (#426) — --step '<STEP>' dentro de um "…" fecha no ' próprio do título, e o lançamento morre antes que qualquer verificador execute. O lançador recebe o título como um elemento de seu argv e o passa como um elemento de verify.sh, então nenhum segundo shell jamais o analisa —

rm -f .tdd/verdict-<fase>.exit .tdd/verdict-<fase>.pid
VERI_TEST_CMD='<cmd-teste>' VERI_ISSUE=<n> VERI_STEP="$(cat .tdd/issue-<n>.md | $S/ledger-steps.sh current)" $S/detach-leaf.sh $S/supervise.sh verdict-<fase> $S/run-verdict.sh --step "$(cat .tdd/issue-<n>.md | $S/ledger-steps.sh current-or-last-done)" <fase> … > .tdd/verify-<fase>.reason 2> .tdd/verify-<fase>.err
$S/wait-leaf.sh verdict-<fase>

— nomeado verdict-<fase>, nunca verify-<fase>, que o próprio supervisor da folha verificadora ya possui. run-verdict.sh executa o verbo de verificação, então o código que o supervisor vê é verify.sh e o arquivo de conclusão que ele registra é o veredicto. wait-leaf.sh então retorna o veredicto: 0 PASS, 1 REJECT, 2 malformed ou killed-stuck. A razão é lida de volta de .tdd/verify-<fase>.reason, o arquivo

…

[truncado para tradução]

Habilidades Relacionadas