O que closeout faz?
Pegue um PR revisado e retorne o workspace a uma base limpa — mescle, mude o shell da worktree antes de removê-la, pode o branch mesclado, puxe a base e verifique o estado final. Esta é a etapa final do pipeline: o movimento mais repetido ("Terminei, mescle e me traga de volta a uma base limpa") sai da cabeça do usuário e vai para um skill.
Por Que Isso Existe
/execute Passo 0 cria uma worktree para cada fatia; até este skill existir, nada removia uma. Worktrees são um estoque com entrada e sem saída, então o estoque cresce sem limites (Meadows, Thinking in Systems — a saída estrutural para um estoque com entrada sem saída é adicionar o loop de feedback ausente, não se esforçar mais). A sequência de mesclagem → desmontagem → pull vivia apenas na memória do usuário, e "a memória prospectiva falha silenciosamente" (Norman, The Design of Everyday Things — mova o conhecimento da cabeça para o mundo). A ordenação é genuinamente propensa a erros: remover uma worktree enquanto o cwd do shell ainda está dentro dela deixa o shell em um diretório destruído, e um hook Stop dependente de node_modules disparando dali emite uma parede de falsos erros MODULE_NOT_FOUND que parecem uma regressão real. A ordem de cd-para-a-base-antes-de-remover no Passo 4 é a correção estrutural exatamente para esse deslize.
Este skill orquestra ferramentas existentes (gh pr merge, wt remove / git worktree remove, commit-commands:clean_gone). Ele não introduz estado no sistema de arquivos. Ele verifica o resultado — árvore limpa, em uma base nova, PR mesclado — não apenas que a limpeza foi executada (Meadows, "buscando o objetivo errado": um closeout medido por "foi executado" otimiza o proxy e produz cerimônia frágil).
Posição de Invocação
Este é um skill primário do pipeline que possui a etapa final de mesclagem → limpeza do caminho de entrega padrão:
… → /pre-merge → /compound (dentro do PR, quando existe uma lição) → /pre-merge (reexecutado sobre o commit composto; re-carimba) → [mesclagem + desmontagem da worktree — ESTE SKILL] → [fechamento de issue — Passo 9 prosa]
Use /closeout quando:
/pre-mergecriou um PR e a revisão está concluída (ou a mudança é trivial o suficiente para não precisar de uma)- o PR está aprovado e mergeável, e você deseja que ele seja mesclado e o workspace retornado a uma base limpa
- o usuário pede para "fechar," "mesclar e limpar," "desmontar a worktree," ou "voltar a uma base limpa"
Não use /closeout quando:
- o PR ainda não foi criado ou ainda está em revisão — execute
/pre-mergeprimeiro - você deseja capturar uma lição durável — isso é
/compound, que por padrão já viajou no PR antes de/closeoutmesclar (pós-mesclagem/compoundé o fallback);/closeoutnão captura lições - você deseja fechar o PRD e as issues da fatia ou reconciliar o frontmatter do artefato de pesquisa — isso é a prosa de limpeza no
SYSTEM-OVERVIEW.mdPasso 9;/closeoutrealiza a metade de higiene do git e adia a metade de fechamento de issue e substituição de artefato de pesquisa para o Passo 9, em vez de duplicá-la. O único fechamento que ele faz é o item de trabalho da fatia mesclada no backendsidecar(Passo 3), porque nenhuma linhaCloses #Nfaz isso na mesclagem - a mudança não está em um branch de feature (você está no branch base sem nada para mesclar) — não há nada para fechar
Ele é HITL-confirmado e não bloqueante: ele operacionaliza uma etapa final já opcional. Ele nunca auto-mescla — a mesclagem é voltada para o exterior e difícil de reverter, então sempre confirma primeiro.
Processo
Execute isso como uma lista de verificação do tipo faça-confirme (Gawande): realize cada passo e verifique seu resultado antes do próximo. Os passos destrutivos (4–6) são autoprotegidos — confirme as pré-condições de segurança no Passo 2 antes de executá-los.
0. Isolamento provisionado pelo host? Ceder a desmontagem primeiro
Antes da sequência de mesclagem/desmontagem, determine se o isolamento deste repositório é de propriedade do host (Conductor, Codespaces, devcontainers). Quando for, /closeout ainda realiza a mesclagem nativa do GitHub, mas não desmonta a worktree nem poda o branch local — o host possui essa metade do ciclo de vida, e lutar contra isso é a resistência política de Meadows (dois atores empurrando o mesmo estoque). Ceda o estoque da worktree ao host.
A detecção de saída é mais estrita do que a verificação de entrada do /execute e não deve usar a heurística genérica "toplevel ≠ árvore de trabalho primária" sozinha: uma worktree criada pelo pipeline também satisfaz esse teste, então o sinal genérico não pode distinguir "host é dono da desmontagem" de "o pipeline fez isso e deve desmontá-lo." Resolva a partir de .claude/settings.json worktree.provisioning (padrão "auto" quando ausente), e honre uma substituição explícita neste final exatamente como /execute faz na entrada:
host— ceder a desmontagem incondicionalmente.pipeline— o pipeline é dono da desmontagem. Execute os Passos 4–6 completos abaixo mesmo se uma variável de ambiente do host estiver presente — uma configuração explícitapipelinesignifica que/executeprovisionou a worktree por conta própria, então/closeoutdeve desmontá-la (o comportamento anterior ao host). Uma variável de ambiente do host não substitui uma escolha explícitapipeline.auto(padrão) — ceder a desmontagem apenas se uma variável de ambiente do host descrever esta árvore: o bloco abaixo imprimehost_owned=yes. Nunca ceder no sinal genérico "toplevel ≠ primária" sozinho, e nunca na mera presença deCONDUCTOR_WORKSPACE_PATH— um shell iniciado sob o Conductor o herda e pode carregar um que nomeia o workspace de um repositório diferente, e ceder com isso deixa uma worktree criada pelo pipeline sem ninguém para desmontá-la.
# host-signal: does a host env var describe THIS tree, not just this shell?
host_owned=no
top=$(cd "$(git rev-parse --show-toplevel)" && pwd -P)
if [ -n "${CONDUCTOR_WORKSPACE_PATH:-}" ]; then
ws=$(cd "$CONDUCTOR_WORKSPACE_PATH" 2>/dev/null && pwd -P) || ws=
if [ -n "$ws" ]; then
case "$top/" in "$ws"/*) host_owned=yes ;; esac
fi
fi
if [ -n "${CODESPACES:-}" ] || [ -n "${REMOTE_CONTAINERS:-}" ]; then host_owned=yes; fi
echo "host_owned=$host_owned"
Se a desmontagem não for cedida (incluindo worktree.provisioning: "auto" com host_owned=no), o pipeline é dono dela — execute os Passos 4–6 completos abaixo como escrito.
Quando o isolamento é de propriedade do host, /closeout executa uma sequência reduzida:
- Passo 3 (mesclagem) ainda é executado por completo —
gh pr merge --delete-branch. Conductor e hosts similares criam o PR, mas deixam a mesclagem para uma ação humana; o pipeline realizar a mesclagem nativa do GitHub é o valor que ele adiciona, e o host reflete o resultado. A delegação é limitada à desmontagem do sistema de arquivos, nunca à mesclagem. - Pular a reancoragem do Passo 4, o
wt remove/git worktree remove/ExitWorktree { remove }do Passo 5 e a poda do branch local do Passo 6 — o host possui o ciclo de vida da worktree e do branch (Conductor os recupera quando o workspace é arquivado). Nunca use--forcecontra uma árvore gerenciada pelo host. - Lista de verificação do Passo 8: "Worktree removida" e "Branch podado" tornam-se "N/A — gerenciado pelo host." As verificações de mesclagem, árvore limpa, PR mesclado e branch remoto ainda se aplicam. O último é fácil de colocar no mesmo N/A e não é: o que o host possui é a worktree do sistema de arquivos e o branch local, enquanto o branch remoto é deletado pelo
gh pr merge --delete-branchque este skill ainda executa por completo no marcador acima. Ceder a metade que o host possui não cede a metade que ele não possui.
1. Confirmar intenção e coletar estado
Nunca auto-mesclar. Declare o que você está prestes a fazer e obtenha uma autorização explícita.
Colete os fatos primeiro (estes são somente leitura):
git branch --show-current # o branch de feature
git rev-parse --show-toplevel # a árvore de trabalho atual (pode ser uma worktree)
git worktree list --porcelain | awk '/^worktree /{print $(2); exit}' # o checkout BASE (árvore de trabalho principal)
gh pr view --json number,title,url,state,mergeable,mergeStateStatus,reviewDecision 2>/dev/null
Determine o branch base que o PR tem como alvo — o padrão declarado do repositório, não uma suposição:
SC="<diretório deste skill>/references/sidecar.sh"
bash "$SC" base-ref --name # o nome do branch: prod, nunca origin/prod
Este local deseja o nome do branch base, não uma ref — ele alimenta git switch <branch-base> no Passo 4, e git switch rejeita uma ref de rastreamento remoto diretamente (fatal: a branch is expected, got remote branch 'origin/prod') — ele quer o branch base local que você está prestes a puxar. Então ele pede base-ref --name, e deliberadamente não a forma de ref que /pre-merge, /help, /walk-commits, /visual-recap, /compound, e /execute usam; eles precisam de uma ref apenas porque usam a base como um ponto final de intervalo, o que este skill nunca faz. Não "corrija" isso para corresponder a eles. Se base-ref --name sair com código diferente de zero, ele não encontrou nenhuma base: repasse seu stderr e pare, em vez de adivinhar um.
Informe ao usuário, em um bloco curto: qual PR será mesclado, qual worktree (se houver) e branch serão desmontados e para qual branch base você retornará e puxará. Aguarde confirmação. Se não houver nenhum PR neste branch, pare e encaminhe para /pre-merge.
2. Verificar pré-condições de segurança antes de qualquer passo destrutivo
A desmontagem é destrutiva; verifique se é segura primeiro (autoproteção). Todas as seguintes devem ser verdadeiras:
- O branch de feature está totalmente enviado —
git statusnão mostra commits não enviados, egit log @{u}..está vazio. - Um PR existe para este branch e é mergeável (
mergeableéMERGEABLE;mergeStateStatusnão éBLOCKED/DIRTY). - As verificações necessárias estão verdes e a revisão está aprovada (
reviewDecisionéAPPROVED, ou o repositório não exige revisão e o usuário aceitou explicitamente).
Se qualquer pré-condição falhar, pare e relate qual — não mescle ou remova nada. Uma pré-condição falha é um sinal para retornar à revisão, não para forçar passagem. Quando a que falhou é a revisão — reviewDecision é CHANGES_REQUESTED — o caminho de volta é /respond-to-review: ele verifica cada comentário contra o código, o humano decide o que corrigir e o que contestar, e as correções passam por /fix-findings e uma reexecução de /pre-merge antes de /closeout ser executado novamente.
Atualidade da revisão — a aprovação ainda cobre este diff? (não bloqueante)
As três caixas de seleção acima perguntam se uma revisão ocorreu. Nenhuma delas pergunta se ela cobriu o diff prestes a ser mesclado. No fluxo linear pretendido, essas são a mesma pergunta, que é exatamente por que a lacuna é invisível; em uma sessão HITL real, o escopo se acumula em um branch após sua passagem de revisão, e nada conecta o diff que /pre-merge leu ao diff que /closeout mescla. Isso é uma entrada sem loop de feedback (Meadows — a correção estrutural é adicionar o loop, não se esforçar), e silenciosamente degrada "o PR foi revisado" para "uma versão inicial do PR foi revisada" no momento em que a degradação mais importa. O Continuous Delivery afirma a regra claramente: o artefato que você revisou deve ser o artefato que você entrega.
Leia o carimbo que /pre-merge Fase 4 deixou e compare com o HEAD atual. Ele reside onde o backend stamp disser, por references/state-backend.md: no corpo do PR, ou no sidecar.
git fetch --quiet
SC="<diretório deste skill>/references/sidecar.sh"
case "$(bash "$SC" backend stamp)" in
sidecar)
REVIEWED_SHA=$(bash "$SC" stamp read "$(git branch --show-current)")
HEAD_SHA=$(git rev-parse '@{u}' 2>/dev/null || git rev-parse HEAD) ;;
github)
REVIEWED_SHA=$(gh pr view <número> --json body -q .body \
| sed -n 's/.*<!-- reviewed-at: \([0-9a-f]\{40\}\) -->.*/\1/p' | tail -1)
HEAD_SHA=$(gh pr view <número> --json headRefOid -q .headRefOid) ;;
*) echo "closeout abortado: sidecar.sh backend stamp falhou; não adivinhando o caminho do github" >&2; exit 1 ;;
esac
No backend sidecar, o HEAD é o upstream do branch, que é o que a forja mesclará: a primeira pré-condição acima já exige que ele corresponda ao HEAD local. Ele recai para HEAD quando o branch não tem upstream. stamp read imprime o mesmo OID completo de 40 caracteres que o marcador no corpo do PR carrega, ou nada, então tudo abaixo é executado inalterado em qualquer backend.
Exatamente 40 caracteres hexadecimais, comparados por igualdade de string. Ambas as metades dessa frase são estruturais, porque este marcador é um contrato que abrange dois skills. /pre-merge Fase 4 emite o OID completo de 40 caracteres, e headRefOid é sempre de 40 caracteres, então \{40\} é a única largura que pode jamais ser igual — aceitar um mais curto deixaria um marcador malformado (um corpo editado manualmente, um placeholder de template substituído na metade errada) ser analisado com sucesso e então diferir de $HEAD_SHA para sempre. [[ "$REVIEWED_SHA" == "$HEAD_SHA" ]] é a comparação: igualdade exata de string em dois OIDs completos, nunca um teste de prefixo. Um marcador que a regex rejeita produz um $REVIEWED_SHA vazio e segue o ramo "nenhum carimbo encontrado" abaixo, que degrada graciosamente — muito melhor do que o ramo "SHAs diferem" gritando lobo em um PR cujo HEAD nunca se moveu. No repositório Skill Kit, scripts/test-review-currency-marker.sh fixa esta ida e volta contra ambos os skills para que o formato do escritor e este analisador não possam se distanciar silenciosamente.
Três resultados, e apenas um deles interrompe:
- Os SHAs coincidem — OIDs completos iguais por string, então o diff revisado é o candidato a mesclagem. Passe silenciosamente. Não narre isso: este é o caso comum, e anunciar um não-evento a cada execução é o que transforma uma comporta em papel de parede.
- Nenhum carimbo encontrado (
$REVIEWED_SHAvazio) — um PR escrito à mão, uma contribuição externa, um PR que antecede o carimbo, ou um marcador cuja forma a regex acima rejeitou. Anote em uma linha ("nenhum carimbo/pre-mergeneste PR — atualidade da revisão desconhecida") e continue. Ausência de um carimbo não é evidência de um diff não revisado, e parar em cada PR sem carimbo é precisamente a fadiga de alarme que faz uma comporta ser derrotada reflexivamente (Meadows, resistência política). - Os SHAs diferem — dois OIDs bem formados de 40 caracteres que não são iguais por string, significando que commits chegaram após a revisão. Não pare,
...
[truncado para tradução]