Que fait merge-briefing ?
Vue d'ensemble
Un merge est un état partagé irréversible, donc il est conditionné à une réponse explicite de l'utilisateur pour chaque PR. La barrière est peu coûteuse à satisfaire formellement et facile à satisfaire faussement : la version honnête nécessite une sonde assez large pour confirmer que "rien n'est ouvert", et la surface où les constatations bloquantes résident réellement — les fils de commentaires inline — est celle qu'un appel gh pr view --json comments,reviews pourtant naturel omet silencieusement.
Cette compétence est le foyer à la demande de cette procédure. La règle Pre-Merge Reporting (ETHOS.md → Rules praxis carries) maintient la barrière ; la liste en six parties de l'étape 4 ci-dessous est la forme du rapport ; les étapes ci-dessous décrivent comment passer de "la PR semble terminée" à une question à laquelle l'utilisateur peut répondre en un mot.
Principe fondamental : le briefing est une affirmation sur l'état de la PR, donc chaque ligne est sondée au moment de la composition — pas rappelée depuis l'ouverture de la PR.
Quand l'utiliser
- Sur le point de demander à l'utilisateur d'approuver un merge.
- Un merge a été demandé et vous n'avez pas encore présenté de briefing lors de ce tour.
- Nouvelle demande après qu'une demande précédente est restée sans réponse, ou après que CI/révision a changé.
- Une PR compagnon, de suivi, ou régénérée est la suivante dans un cluster que l'utilisateur a déjà approuvé une fois.
La Loi de Fer
- Une PR, un briefing, une réponse. Approuver la PR X n'approuve que X. La seule exception est le merge groupé : un tour avec une section de briefing séparée par PR, une seule question qui les liste, et une réponse qui approuve l'ensemble listé.
- Composez le briefing à partir des résultats des sondages de ce tour. Un statut
CLEANau moment de l'entrée n'est pas permanent ; une ancre obsolète fait découvrir à l'utilisateur un conflit que vous auriez dû détecter. - Ne mergez jamais sur un signal de progression. "계속", "ok", "progress" font avancer le travail ; ils ne constituent pas un consentement pour une mutation d'état partagé.
Procédure
Étape 1 : Sonder les trois surfaces
Les constatations résident sur trois surfaces et aucune commande unique ne les couvre. Exécutez les trois avant d'écrire un mot du briefing.
# 1 — CI
gh pr checks <N> --repo <owner>/<repo>
# 2 — timeline de conversation + corps de revues + fusionnabilité + HEAD poussé
gh pr view <N> --repo <owner>/<repo> \
--json mergeable,mergeStateStatus,isDraft,headRefOid,reviews,comments
# 3 — fils de commentaires inline (la surface que #2 N'inclut PAS)
gh api graphql --paginate -f query='
query($owner:String!, $repo:String!, $pr:Int!, $endCursor:String) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$pr) {
reviewThreads(first:100, after:$endCursor) {
pageInfo { hasNextPage endCursor }
nodes {
id isResolved isOutdated path line
comments(first:1) { nodes { author { login } body } }
}
}
}
}
}' -F owner=<owner> -F repo=<repo> -F pr=<N> \
--jq '.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved | not)
| {id, path, line, author: .comments.nodes[0].author.login,
body: .comments.nodes[0].body}'
body est dans la projection car l'étiquette se trouve dans le texte du fil lui-même, et l'étiquette est ce sur quoi l'étape 2 se base. --paginate avec $endCursor n'est pas une décoration — une PR sur laquelle deux bots ont travaillé dépasse 100 fils, et une seule page ignore silencieusement le reste, ce qui est alors interprété comme "aucun fil ouvert".
La surface #2 tronque également à 100, et silencieusement. gh pr view --json comments,reviews émet comments(first: 100) / reviews(first: 100) sans variable de curseur, donc sur une PR dépassant l'un ou l'autre compte, la fin est simplement absente. Lorsque l'un d'eux atteint 100 lignes, recollectez cette surface via le point de terminaison REST paginé avant d'évaluer :
github api repos/<owner>/<repo>/pulls/<N>/reviews --paginate -q '.[] | {user: .user.login, state, body}'
github api repos/<owner>/<repo>/issues/<N>/comments --paginate -q '.[] | {user: .user.login, body}'
Un résultat vide n'est pas encore une réponse. Il signifie à la fois "aucun fil ouvert" et "la requête est erronée" jusqu'à ce que vous ayez vu cette même requête afficher des lignes sur une PR connue pour en avoir. Exécutez-la une fois contre une telle PR ; c'est le contrôle positif sur lequel repose l'affirmation négative.
Dans un fork clone, --repo doit nommer le fork sur chaque sonde : un appel gh pr nu résout par rapport au dépôt parent (voir worktree-merge-cleanup → Liste de vérification pré-appel).
Si CI est en attente ou en échec, arrêtez-vous ici — ne demandez pas encore. Un briefing dont les lignes de vérification sont encore en mouvement demande à l'utilisateur d'approuver une supposition.
Lisez quel commit chaque approbation humaine couvre avant d'écrire "approuvé" : reviewDecision: APPROVED survit aux poussées ultérieures. Groupez les revues par réviseur et prenez la revue la plus récente APPROVED de chacun (une approbation antérieure est remplacée par une ré-approbation ; un CHANGES_REQUESTED ou DISMISSED ultérieur du même réviseur signifie aucune approbation actuelle). Comparez le commit_id de cette revue avec headRefOid et listez les commits non-merge après celui-ci (git log --no-merges <commit_id>..<head>) dans la partie 5 comme "non revu par " (voir la ligne des Modes d'Échec pour la commande).
L'état de fusion est une liste blanche, pas une vérification "pas manifestement cassé". Demandez uniquement lorsque mergeable est MERGEABLE et mergeStateStatus est CLEAN ou HAS_HOOKS — ces deux valeurs sont les seules qui signifient fusionnable avec un statut de commit réussi. Toute autre valeur arrête la demande et se nomme elle-même dans le briefing :
mergeStateStatus | Signification | Demander ? |
|---|---|---|
CLEAN | Fusionnable et statut de commit réussi | Oui |
HAS_HOOKS | Fusionnable, réussi, hooks de pré-réception présents | Oui |
UNSTABLE | Fusionnable avec un statut de commit non réussi | Non — c'est le CI en échec ci-dessus |
BLOCKED | Merge bloqué (règle de protection, revue requise) | Non |
BEHIND | La référence de tête est obsolète | Non — rebasez d'abord |
DIRTY | Le commit de merge ne peut pas être créé proprement | Non — conflit |
UNKNOWN | L'état ne peut pas être déterminé actuellement | Non — re-sondez |
Le statut de brouillon n'est pas dans cette énumération — c'est le champ séparé isDraft, donc une PR en brouillon peut signaler CLEAN. Ajoutez isDraft à la liste --json et traitez un brouillon comme non prêt quel que soit son état de fusion.
CLEAN ne dit rien sur les commits de base qui sont arrivés depuis la création de la branche. Comptez-les (git fetch origin puis git rev-list --count HEAD..origin/<base>) et listez les fichiers qu'ils ont touchés et que la PR touche également (git diff --name-only HEAD...origin/<base>). Pour chaque fichier partagé, cherchez dans ce diff de base les symboles que cette PR modifie et exécutez git merge-tree --write-tree HEAD origin/<base> — zéro symbole trouvé et code de sortie 0 est la preuve "aucun chevauchement sémantique", citée dans la partie 5. Lorsque le déclencheur de rebase pré-merge se produit (3+ commits de base, un cycle de revue terminé), ramenez la base avec gh pr update-branch <N> (un commit de merge, jamais une réécriture), ce qui redémarre CI et la revue du bot : attendez les deux sur le nouveau HEAD, ré-analysez l'ancre avec le HEAD qui a bougé, puis ré-exécutez les étapes 1 à 3 sur le nouveau HEAD et reconstruisez le briefing avant de demander — les constatations, l'état de fusion et la couverture d'approbation collectées plus tôt décrivent l'ancienne tête.
Étape 2 : Évaluer chaque constatation sur les trois surfaces
Un nombre de fils ouverts n'est pas un nombre de bloqueurs, et la surface des fils n'est pas l'ensemble du corpus. Évaluez chaque constatation collectée à l'étape 1 — fil inline, commentaire de conversation, et corps de revue — par le décor, pas l'étiquette :
| Première ligne de la constatation | Bloque le merge ? |
|---|---|
issue (bloquant): / suggestion (bloquant): | Oui — le merge attend |
issue (non-bloquant): / suggestion (non-bloquant): | Non |
nitpick: / note: | Non — non résolu par conception |
| Pas d'étiquette du tout | Évaluez vous-même — lisez le corps |
suggestion n'est pas un échelon inférieur à issue : les deux portent exactement un (bloquant) / (non-bloquant), donc le décor est le jugement entier et l'étiquette dit seulement de quel type de constatation il s'agit. Lire l'étiquette à la place est ainsi qu'un bloqueur déclaré est laissé passer comme "juste une suggestion".
Un corps de revue n'est pas un fil et ne peut pas être résolu, ce qui est exactement pourquoi il reste non lu : rien ne semble ouvert. CodeRabbit dépose ses constatations exploitables dans des sections repliables du corps de revue plutôt que comme des commentaires de ligne, donc cherchez dans les corps collectés à l'étape 1 les mentions Actionable, Nitpick, outside diff, 🧹 et évaluez ce que vous trouvez. Un commentaire de conversation soulevant un défaut est évalué de la même manière. Les constatations bloquantes sur l'une ou l'autre surface arrêtent la demande tout comme une constatation inline — elles n'ont simplement aucun fil à laisser ouvert, donc elles sont portées dans les éléments ouverts du briefing à la place.
Réconciliez le compte CodeRabbit avant que l'évaluation ne soit terminée : comptez ses constatations par les marqueurs `LINE`: ** dans les corps et faites correspondre le total que l'en-tête de la revue indique (par exemple Major comments: N). Répondez à l'ensemble en un commentaire de conversation groupé Corrigé — <sha> (n) / Non corrigé (n) / Faux positif (n), dont les comptes additionnent ce total.
Le cas non étiqueté est le cas courant, pas l'exception : les bots écrivent dans leur propre vocabulaire. CodeRabbit commence par sa propre ligne de sévérité (_🎯 Functional Correctness_ | _🟠 Major_ | …), Copilot et BugBot sans rien. Traitez la sévérité d'un bot comme une entrée pour votre évaluation, jamais comme l'évaluation — et jamais comme une raison pour ignorer la constatation parce que "c'est juste un bot".
Un fil que vous avez évalué comme bloquant a une seule sortie légitime avant le merge : il est corrigé et fermé, ou il est ré-évalué — en modifiant le commentaire pour (non-bloquant) et en postant l'avis Révisé —. Laisser une constatation bloquante dans un suivi sans la ré-évaluer verrouille la branche alors que chaque étiquette se lit comme non-bloquante.
Effacer le REQUEST_CHANGES au niveau de la revue est une étape ultérieure et séparée. Cet état appartient à la revue, qui peut contenir plusieurs constatations, donc il est effacé seulement une fois que chaque constatation bloquante qu'elle contient est corrigée-et-fermée ou ré-évaluée — jamais sur la force d'une seule. La ré-évaluation de la dernière est ce qui la déverrouille ; jusque-là, la revue bloque correctement encore.
Étape 3 : Reporter les lacunes Unverified de l'ancre
Ouvrez l'ancre de vérification de la PR et lisez sa liste Unverified. Une lacune écrite comme "머지 후 실전송으로 확인" meurt avec la PR à moins que quelque chose ne lui survive.
- Signalez exactement les lacunes qui nommaient une action suivante — une lacune sans action suivante indiquée n'est pas reportée.
- Demandez, dans le cadre du briefing, s'il faut ouvrir un issue de suivi.
- Écrivez la réponse dans la révision finale de l'ancre comme
Reporté : #NouReporté : aucun — <raison>avant le merge. - L'écriture de cette révision finale nécessite
gh— une session dont la seule surface GitHub est le serveur MCP ne peut pas mettre à jour le corps d'un commentaire lorsque la liste d'outils actifs de ce serveur n'expose aucune mise à jour de commentaireupdate(confirmez avant de supposer). Dans ce cas, placez la ligneReporté :dans le commit de merge ou le corps de la PR, et jamais dans un deuxième commentaire d'ancre. Voiranchor-comment-gate/spec.md→ Procedure for a gh-less session. - Ne créez jamais automatiquement le suivi : un nouvel issue a encore besoin de sa propre revue d'approche d'implémentation d'abord.
- Lorsque le suivi est enregistré comme un commentaire à la place (
Reporté : <URL du commentaire>), placez-le sur un issue qui reste ouvert après le merge — l'issueRef:de la PR (par exemple un issue hub/parapluie fermé plus tard par une étape de release), jamais l'issueCloses #N, que le merge lui-même ferme et que personne ne rouvre. La même règle s'applique aux notes hors de portée écrites pendant que la PR est ouverte. - Lorsque l'issue de report est celui qu'une étape de release post-merge ferme (un issue hub fermé par
/release), la release enterre le report exactement comme le ferait une cibleCloses. Nommez ce conflit dans le rapport de merge (releasez maintenant et perdez les éléments ouverts, ou releasez après qu'ils soient vérifiés) plutôt que de le résoudre silencieusement d'une manière ou d'une autre. Vérifiez également la cibleCloses #Naprès le merge : GitHub ne la ferme que lorsque la PR est mergée dans la branche par défaut du dépôt, donc une PR vers une base non par défaut (prodalors que la valeur par défaut estdev) la laisse ouverte pour l'étape de release.
Étape 4 : Composer le briefing en six parties
Six parties, dans cet ordre (cette liste est la forme canonique ; elle a été initialement dérivée du modèle de workflow PR de l'auteur) :
- Ce qui a changé — ouvrez par une phrase simple de ce que fait la PR et ce qui change pour l'utilisateur si elle est mergée (et si elle ne l'est pas), puis le périmètre (fichiers, changements logiques), pas le titre de l'issue à nouveau. Une partie 1 composée de chiffres et de termes internes est suivie d'un "que fait-elle ?" au lieu d'une réponse, ce qui réinitialise la fenêtre d'approbation et coûte une réintégration complète.
- Ce qui a été vérifié — résultat réel, avec la commande citée
- Ce qui n'a PAS été vérifié — ignoré, différé, bloqué, en attente de CI
- Risque / rayon d'impact — qui casse si c'est faux
- Éléments ouverts — chaque constatation que l'étape 2 a évaluée et laissée ouverte, sur les trois surfaces ; les lacunes de l'étape 3
- Demande explicite —
Approuver le merge ?sur sa propre ligne, dans le texte, avec l'appelAskUserQuestionde l'étape 5 juste après (la ligne reste même si l'outil demande, car c'est l'élément sur lequel la barrière note)
La partie 3 est celle sous pression, et c'est la raison pour laquelle le briefing existe.
Lorsqu'un élément non vérifié est précisément l'étape que le merge déclenche (un déploiement post-merge, une construction d'actif que CI n'a jamais exécutée, une construction multi-plateforme que vous avez exécutée sur une seule plateforme), c'est un risque, pas une note de bas de page : mettez-le dans la partie 4 avec ce qui casse si cette étape échoue et qui d'autre cela bloque (déploiements ultérieurs de la même pile). Pour une PR dont la valeur est de la tenue de registres (verrous de version, catalogues, documentation), la partie 4 dit également en une ligne ce qui se passe si elle n'est pas mergée — l'utilisateur demande cela avant d'approuver une PR à faible enjeu qui déclenche néanmoins un déploiement.
Anti-patrons de la partie 3 : "완료했습니다, 머지할까요?" sans preuve ; une vérification ignorée reléguée à un aparté en fin de document ; signaler au moment du merge ce qui aurait dû être présenté au moment où la PR était prête.
Fermez les éléments de la partie 3 en observant, pas en lisant. Lorsque l'utilisateur vous demande de vérifier les éléments non vérifiés, allez élément par élément et exécutez la sonde la moins coûteuse qui règle la question. Le résultat in situ — une vérification de déploiement qui a depuis réussi, un son de page qui a chargé — est la preuve ; relire le plan de test qui a écrit l'élément est la lecture.
Étape 5 : Demander via AskUserQuestion
L'appel d'outil suit immédiatement la partie 6 ; il est une question unique sur l'état observable du système tout entier, pas un ensemble de questions de portée variable. AskUserChat et AskUserQuestion sont l'un et l'autre acceptables tant que la question est : "Approuver le merge de PR #N ?" — sans incident, scénario, contexte ou prochaine étape.
Quand la réponse est non, demandez des instructions de suivi avant de supposer une instruction spécifique. Si l'utilisateur a répondu plus tôt et que la PR n'a pas changé entre-temps, AskUserQuestion se bloque et AskUserChat reprend la conversation.
Étape 6 : Charger l'étape suivante
| L'utilisateur a dit | Action |
|---|---|
| "Oui" / "Approuve" | Mergez (gh pr merge <N> --merge --subject ""). Note : une stratégie de merge doit être définie sauf si le dépôt est configuré pour en autoriser une seule ; utilisez --squash ou --rebase si la configuration du dépôt l'exige. Le sujet vide force GitHub à accepter le message de merge par défaut ou le message de titre + body standard, selon la configuration. N'utilisez jamais --delete-branch (c'est l'affaire du script de nettoyage). |
| "Non" | Annulez. Écrivez l'appel de merge dans le journal : "Annonce PR #N — utilisateur a refusé. Raison donnée : <citation de la réponse>." Aucune nouvelle demande ce tour. |
| Conditionnel ("si CI passe", "une fois que la revue est terminée") | Rédigez une observation qui vérifie la condition et déposez-la comme commentaire de suivi, puis annulez ce tour — la condition n'est pas maintenant. |
Le champ --subject vide est intentionnel : un message de merge vide médiatise moins le merge ; l'utilisateur peut toujours le modifier dans l'interface utilisateur. Supprimez tout message modèle que GitHub pourrait insérer. Voir commit-message-style/spec.md → merge commits.
Merge groupé : plusieurs PRs, une réponse
Un cluster approuvé une fois contient des PRs qui partagent une frontière logique (un problème nécessitant plusieurs PRs de correctifs), un déclencheur de déploiement, ou une fenêtre de release. La procédure standard ne change pas :
- Exécutez les étapes 1 à 4 sur chaque PR du cluster. Si l'une d'elles échoue à l'étape 1, n'allez pas plus loin — aucune PR ne sera mergée ce tour.
- Composez une partie 5 qui contient des sous-sections de briefing séparées, une par PR, dans l'ordre : N° de PR, partie 1, partie 2, partie 3, partie 5 (pas de partie 4 séparée — une seule partie 4 pour le cluster entier).
- Composez une partie 6 qui nomme toutes les PR que la réponse approuvera : "Approuver le merge de PR #N₁, #N₂, #N₃ ?".
- L'utilisateur répond à la question du cluster ; si oui, mergez chaque PR individuellement. Si l'une des PR échoue au merge (conflit né, hook de pré-réception rejeté), annulez les PRs restantes et informez-en.
Échecs de la partie 3
Une revue qui approuve alors que la partie 3 signale des éléments non vérifiés est une revue sans ancre. Le processus est le même dans les deux sens : l'ancre est ce qui rend le briefing une observation plutôt qu'une relecture de plan. Lorsque vous recevez un briefing de revue dont la partie 3 est vide parce que la PR a été mergée rapidement ou parce que l'auteur du brief n'avait pas accès, déposez vos propres sondes de la partie 3 comme pièces jointes. Une ancre vide n'est pas une ancre.
Modes d'échec
Problèmes courants qui ne sont pas des erreurs de protocole mais qui font que le merge échoue à produire un résultat correct :
| Problème | Symptôme | Cause | Solution |
|---|---|---|---|
| Constatation de bot invisible | CodeRabbit / Copilot trouve un défaut, mais le fil n'apparaît pas dans reviewThreads | Le défaut est dans le corps de la revue (une section repliable), pas comme commentaire de ligne | Utilisez l'étape 2 — cherchez dans les corps de revue Actionable, 🧹 |
| Réponse d'approbation obsolète | Le merge réussit, puis CI échoue | reviewDecision: APPROVED survit aux nouveaux commits ; une revue plus ancienne montrait toujours APPROVED | Vérifiez toujours commit_id de la revue la plus récente par rapport à headRefOid |
| Liste de commentaires tronquée | Un commentaire au milieu de la conversation n'est pas dans le briefing | gh pr view --json comments est limité à 100 — le commentaire était le 101e | Re-collectez via l'API REST paginée avant l'évaluation |
| La PR n'est pas fusionnable mais le briefing dit le contraire | L'utilisateur approuve, puis gh pr merge échoue avec une erreur en amont | L'état mergeable a changé entre la sonde et le merge | Le merge ne peut pas échouer ici — c'est un échec de sonde. Ré-exécutez l'étape 1 avant d'agir sur la réponse. |
| Ancre non écrite | Le briefing utilise les commits de la revue d'hier | La conversation a continué en privé ou via un autre canal | L'étape 1 échoue sur la partie 2 déjà — expliquez pourquoi et ne proposez pas de merge |
Commandes de diagnostic
Commandes utiles pour résoudre les problèmes d'approbation qui se chevauchent :
# Vérifier le commit_id d'une revue approuvée la plus récente par réviseur
git log --no-merges <commit_id>..<head>
# Lister les commits de base non vus par la PR (pour l'évaluation de chevauchement sémantique)
git fetch origin
git rev-list --count HEAD..origin/<base>
# Vérifier le chevauchement de fichiers entre la PR et les nouveaux commits de base
git diff --name-only HEAD...origin/<base>