Que fait closeout ?
Prenez une PR revue et remettez l'espace de travail sur une base propre — fusionnez, basculez le shell hors du worktree avant de le supprimer, nettoyez la branche fusionnée, récupérez la base et vérifiez l'état final. C'est la partie terminale du pipeline : l'action la plus répétée ("j'ai fini, fusionne et remets-moi sur une base propre") sortie de la tête de l'utilisateur et placée dans une compétence.
Pourquoi Cette Compétence Existe
/execute Étape 0 crée un worktree pour chaque slice ; jusqu'à l'existence de cette compétence, rien n'en supprimait un. Les worktrees sont un stock avec une entrée et aucune sortie, donc le stock augmente sans limite (Meadows, Thinking in Systems — l'échappatoire structurelle pour un stock à entrée sans sortie est d'ajouter la boucle de rétroaction manquante, pas d'exhorter plus fort). La séquence fusion → démontage → récupération ne vivait que dans la mémoire de l'utilisateur, et "la mémoire prospective échoue silencieusement" (Norman, The Design of Everyday Things — déplacer la connaissance de la tête vers le monde). L'ordre est vraiment sujet aux erreurs : supprimer un worktree alors que le répertoire de travail du shell est encore dedans laisse le shell sur un répertoire supprimé, et un hook Stop dépendant de node_modules déclenché depuis là génère un mur de faux "MODULE_NOT_FOUND" "lint/type errors" qui ressemblent à une vraie régression. Le cd-vers-base-avant-suppression dans l'Étape 4 est le correctif structurel pour exactement ce glissement.
Cette compétence orchestre des outils existants (gh pr merge, wt remove / git worktree remove, commit-commands:clean_gone). Elle n'introduit aucun état du système de fichiers. Elle vérifie le résultat — arbre propre, sur une base fraîche, PR fusionnée — pas seulement que le nettoyage a été exécuté (le "mauvais objectif" de Meadows : un closeout mesuré par "a-t-il été exécuté" optimise le proxy et produit une cérémonie fragile).
Position d'Invocation
C'est une compétence de pipeline principale qui possède la partie terminale fusion → nettoyage du chemin de livraison par défaut :
… → /pre-merge → /compound (dans la PR, quand une leçon existe) → /pre-merge (ré-exécution sur le commit compound ; re-timbre) → [fusion + démontage worktree — CETTE COMPÉTENCE] → [fermeture des issues — prose de l'Étape 9]
Utilisez /closeout quand :
/pre-mergea créé une PR et la revue est terminée (ou la modification est assez triviale pour ne pas en nécessiter une)- la PR est approuvée et fusionnable, et vous voulez qu'elle soit fusionnée et l'espace de travail remis sur une base propre
- l'utilisateur demande de "clôturer", "fusionner et nettoyer", "démonter le worktree", ou "revenir sur une base propre/saine"
N'utilisez pas /closeout quand :
- la PR n'est pas encore créée ou est toujours en cours de revue — exécutez
/pre-merged'abord - vous voulez capturer une leçon durable — c'est le rôle de
/compound, qui par défaut a déjà chevauché la PR avant que/closeoutne fusionne (le/compoundpost-fusion est la solution de repli) ;/closeoutne capture pas de leçons - vous voulez fermer le PRD et les issues des slices ou réconcilier le frontmatter de l'artefact de recherche — c'est la prose de nettoyage dans
SYSTEM-OVERVIEW.mdÉtape 9 ;/closeoutréalise la moitié d'hygiène git et reporte la moitié de fermeture des issues et de remplacement des artefacts de recherche à l'Étape 9 plutôt que de la dupliquer. La seule fermeture qu'il fait est celle de l'élément de travail du slice fusionné sur le backendsidecar(Étape 3), car là, aucune ligneCloses #Nne le fait lors de la fusion - la modification n'est pas du tout sur une branche de fonctionnalité (vous êtes sur la branche de base sans rien à fusionner) — il n'y a rien à clôturer
Elle est confirmée par un humain dans la boucle et non-bloquante : elle opérationnalise une partie terminale déjà facultative. Elle ne fusionne jamais automatiquement — la fusion est tournée vers l'extérieur et difficile à annuler, donc elle confirme toujours d'abord.
Processus
Exécutez-la comme une checklist à faire-confirmer (Gawande) : effectuez chaque étape, puis vérifiez son résultat avant la suivante. Les étapes destructrices (4–6) sont auto-protégées — confirmez les préconditions de sécurité de l'Étape 2 avant de les exécuter.
0. Isolement fourni par l'hôte ? Cédez le démontage d'abord
Avant la séquence fusion/démontage, déterminez si l'isolement de ce dépôt est propre à l'hôte (Conductor, Codespaces, devcontainers). Quand c'est le cas, /closeout effectue toujours la fusion native GitHub mais ne démonte pas le worktree et ne nettoie pas la branche locale — l'hôte possède cette moitié du cycle de vie, et lutter contre c'est la résistance aux politiques de Meadows (deux acteurs poussant le même stock). Cédez le stock de worktree à l'hôte.
La détection de la sortie est plus stricte que la vérification d'entrée de /execute et ne doit pas utiliser le seul heuristique générique "toplevel ≠ arbre de travail principal" : un worktree créé par le pipeline satisfait également ce test, donc le signal générique ne peut pas distinguer "l'hôte possède le démontage" de "le pipeline a créé ceci et doit le démonter." Résolvez à partir de .claude/settings.json worktree.provisioning ("auto" par défaut quand absent), et honorez une surcharge explicite à cette fin exactement comme /execute le fait à l'entrée :
host— cédez le démontage inconditionnellement.pipeline— le pipeline possède le démontage. Exécutez les Étapes 4–6 complètes ci-dessous même si une variable d'environnement hôte est présente — un paramètre explicitepipelinesignifie que/executea provisionné le worktree lui-même, donc/closeoutdoit le démonter (le comportement pré-hôte). Une variable d'environnement hôte ne remplace pas un choix explicitepipeline.auto(par défaut) — cédez le démontage seulement si une variable d'environnement hôte décrit cet arbre : le bloc ci-dessous affichehost_owned=yes. Ne cédez jamais sur le seul signal générique "toplevel ≠ primary", et jamais sur la simple présence deCONDUCTOR_WORKSPACE_PATH— un shell démarré sous Conductor l'hérite et peut en porter un nommant l'espace de travail d'un autre dépôt, et céder sur cela laisse un worktree créé par le pipeline sans personne pour le démonter.
# host-signal: une variable d'environnement hôte décrit-elle CET arbre, pas seulement ce 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"
Si le démontage n'est pas cédé (incluant worktree.provisioning: "auto" avec host_owned=no), le pipeline le possède — exécutez les Étapes 4–6 complètes ci-dessous comme écrit.
Quand l'isolement est propre à l'hôte, /closeout exécute une séquence réduite :
- L'Étape 3 (fusion) s'exécute toujours en entier —
gh pr merge --delete-branch. Conductor et les hôtes similaires créent la PR mais laissent la fusion à une action humaine ; le pipeline effectuant la fusion native GitHub est la valeur qu'il ajoute, et l'hôte reflète le résultat. Le report est limité au démontage du système de fichiers, jamais à la fusion. - Sautez le ré-ancrage de l'Étape 4, le
wt remove/git worktree remove/ExitWorktree { remove }de l'Étape 5, et le nettoyage de la branche locale de l'Étape 6 — l'hôte possède le worktree et le cycle de vie de la branche (Conductor les récupère quand l'espace de travail est archivé). N'utilisez jamais--forcecontre un arbre géré par l'hôte. - Checklist de l'Étape 8 : "Worktree supprimé" et "Branche nettoyée" deviennent "N/A — géré par l'hôte." Les vérifications de fusion, d'arbre propre, de PR fusionnée et de branche distante s'appliquent toujours. Cette dernière est facile à classer sous le même N/A et ne l'est pas : ce que l'hôte possède, c'est le worktree système de fichiers et la branche locale, tandis que la branche distante est supprimée par le
gh pr merge --delete-branchque cette compétence exécute toujours en entier un point plus haut. Céder la moitié que l'hôte possède ne cède pas la moitié qu'il ne possède pas.
1. Confirmer l'intention et rassembler l'état
Ne jamais fusionner automatiquement. Énoncez ce que vous êtes sur le point de faire et obtenez un feu vert explicite.
Rassemblez d'abord les faits (ce sont des lectures seules) :
git branch --show-current # la branche de fonctionnalité
git rev-parse --show-toplevel # l'arbre de travail actuel (peut-être un worktree)
git worktree list --porcelain | awk '/^worktree /{print $(2); exit}' # le checkout BASE (arbre de travail principal)
gh pr view --json number,title,url,state,mergeable,mergeStateStatus,reviewDecision 2>/dev/null
Déterminez la branche de base que la PR cible — la valeur par défaut déclarée du dépôt, pas une supposition :
SC="<ce répertoire de compétence>/references/sidecar.sh"
bash "$SC" base-ref --name # le nom nu de la branche : prod, jamais origin/prod
Ce site veut le nom de la branche de base, pas une ref — il alimente git switch <base-branch> à l'Étape 4, et git switch rejette catégoriquement une ref de suivi distant (fatal: a branch is expected, got remote branch 'origin/prod') — il veut la branche de base locale que vous êtes sur le point de récupérer. Donc il demande base-ref --name, et délibérément pas la forme ref que /pre-merge, /help, /walk-commits, /visual-recap, /compound, et /execute prennent ; ils n'ont besoin d'une ref que parce qu'ils utilisent la base comme point de terminaison de plage, ce que cette compétence ne fait jamais. Ne "corrigez" pas cela pour correspondre. Si base-ref --name se termine avec un code non nul, il n'a trouvé aucune base : relayez son stderr et arrêtez-vous plutôt que d'en deviner une.
Dites à l'utilisateur, en un court bloc : quelle PR sera fusionnée, quel worktree (le cas échéant) et quelle branche seront démontés, et sur quelle branche de base vous reviendrez et que vous récupérerez. Attendez la confirmation. S'il n'y a pas de PR sur cette branche, arrêtez-vous et orientez vers /pre-merge.
2. Vérifier les préconditions de sécurité avant toute étape destructrice
Le démontage est destructeur ; vérifiez d'abord qu'il est sûr (auto-protection). Tout cela doit être vrai :
- La branche de fonctionnalité est entièrement poussée —
git statusne montre aucun commit non poussé, etgit log @{u}..est vide. - Une PR existe pour cette branche et est fusionnable (
mergeableestMERGEABLE;mergeStateStatusn'est pasBLOCKED/DIRTY). - Les vérifications requises sont vertes et la revue est approuvée (
reviewDecisionestAPPROVED, ou le dépôt n'exige pas de revue et l'utilisateur a explicitement accepté).
Si une précondition échoue, arrêtez-vous et signalez laquelle — ne fusionnez ni ne supprimez rien. Une précondition échouée est un signal pour revenir en revue, pas pour forcer le passage. Quand celle qui a échoué est la revue — reviewDecision est CHANGES_REQUESTED — le chemin de retour est /respond-to-review : il vérifie chaque commentaire par rapport au code, l'humain décide quoi corriger et quoi contester, et les correctifs passent par /fix-findings et une ré-exécution de /pre-merge avant que /closeout ne soit exécuté à nouveau.
Fraîcheur de la revue — l'approbation couvre-t-elle toujours cette diff ? (non-bloquant)
Les trois cases à cocher ci-dessus demandent si une revue a eu lieu. Aucune ne demande si elle couvrait la diff sur le point d'être fusionnée. Dans le flux linéaire prévu, ce sont la même question, ce qui explique exactement pourquoi l'écart est invisible ; dans une session HITL réelle, la portée s'accumule sur une branche après son passage en revue, et rien ne relie la diff que /pre-merge a lue à la diff que /closeout fusionne. C'est une entrée sans boucle de rétroaction (Meadows — le correctif structurel est d'ajouter la boucle, pas d'exhorter), et cela dégrade silencieusement "la PR a été revue" en "une version précoce de la PR a été revue" au moment même où la dégradation importe le plus. Continuous Delivery énonce la règle clairement : l'artefact que vous avez revu doit être l'artefact que vous livrez.
Lisez le timbre que /pre-merge Phase 4 a laissé et comparez-le à la tête actuelle. Il se trouve là où backend stamp dit, selon references/state-backend.md : dans le corps de la PR, ou dans le sidecar.
git fetch --quiet
SC="<ce répertoire de compétence>/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 <number> --json body -q .body \
| sed -n 's/.*<!-- reviewed-at: \([0-9a-f]\{40\}\) -->.*/\1/p' | tail -1)
HEAD_SHA=$(gh pr view <number> --json headRefOid -q .headRefOid) ;;
*) echo "closeout aborté : sidecar.sh backend stamp a échoué ; ne pas deviner le chemin github" >&2; exit 1 ;;
esac
Sur le backend sidecar, la tête est la branche amont de la branche, qui est ce que la forge va fusionner : la première précondition ci-dessus exige déjà qu'elle corresponde à la tête locale. Elle tombe sur HEAD quand la branche n'a pas d'amont. stamp read affiche le même OID complet de 40 caractères que le marqueur du corps de la PR, ou rien, donc tout ce qui suit s'exécute inchangé sur l'un ou l'autre backend.
Exactement 40 caractères hexadécimaux, comparés par égalité de chaîne. Les deux moitiés de cette phrase sont structurelles, car ce marqueur est un contrat qui s'étend sur deux compétences. /pre-merge Phase 4 émet l'OID complet de 40 caractères, et headRefOid est toujours de 40 caractères, donc \{40\} est la seule largeur qui puisse jamais être égale lors de la comparaison — accepter une plus courte laisserait un marqueur malformé (un corps édité à la main, un placeholder de template substitué dans la mauvaise moitié) être analysé avec succès pour ensuite différer de $HEAD_SHA pour toujours. [[ "$REVIEWED_SHA" == "$HEAD_SHA" ]] est la comparaison : égalité exacte de chaîne sur deux OID complets, jamais un test de préfixe. Un marqueur que l'expression régulière rejette donne un $REVIEWED_SHA vide et prend la branche "aucun timbre trouvé" ci-dessous, qui se dégrade gracieusement — bien mieux que la branche "les SHA diffèrent" qui crie au loup sur une PR dont la tête n'a jamais bougé. Dans le dépôt Skill Kit, scripts/test-review-currency-marker.sh épingle cet aller-retour contre les deux compétences afin que le format du rédacteur et cet analyseur ne puissent pas dériver séparément silencieusement.
Trois résultats, et un seul d'entre eux interrompt :
- Les SHA correspondent — OID complets égaux en chaîne, donc la diff revue est le candidat à la fusion. Passez silencieusement. Ne le narrisez pas : c'est le cas courant, et annoncer un non-événement à chaque exécution est ce qui transforme une barrière en papier peint.
- Aucun timbre trouvé (
$REVIEWED_SHAvide) — une PR rédigée à la main, une contribution externe, une PR antérieure au timbre, ou un marqueur dont la forme a été rejetée par l'expression régulière ci-dessus. Notez-le en une ligne ("aucun timbre/pre-mergesur cette PR — fraîcheur de la revue inconnue") et continuez. L'absence d'un timbre n'est pas une preuve d'une diff non revue, et s'arrêter sur chaque PR non timbrée est précisément la fatigue d'alarme qui fait qu'une barrière est reflexivement contournée (Meadows, résistance aux politiques). - Les SHA diffèrent — deux OID bien formés de 40 caractères qui ne sont pas égaux en chaîne, ce qui signifie que des commits sont arrivés après la revue. Ne vous arrêtez pas,
…
[suite tronquée pour la traduction]