Skip to content

check_unaddressed_nits: classify() rend None sur un refus d'approbation hors vocabulaire (sous-blocage du merge-gate) #14553

Description

@myia-ai-01

Le defaut

classify(author, body) dans scripts/check_unaddressed_nits.py rend None — donc « aucune reserve » — sur une review qui refuse explicitement d'approuver, quand le refus est formule hors du vocabulaire des 21 CONCERN_MARKERS.

L'organe est le merge-gate B.0 de toute la flotte. Un None ici ne dit pas « la review est benigne » : il dit « le refus n'etait pas ecrit dans mon vocabulaire ». Les deux se lisent exit 0 de l'exterieur.

Mesure — controle negatif et controle positif

Sonde directe du module (importlib), sur les corps de review reels tires de gh pr view --json reviews, le 2026-09-04T01:1xZ :

PR Auteur state Queue du corps classify()
#14535 clusterManager-Myia COMMENTED ...donnees exactes, discipline de provenance en defaut — pas de LGTM en l'etat. None
#14493 clusterManager-Myia COMMENTED ...la structure procedurale est bonne, seules les references du recit sont a corriger. BOT-CONCERN

Le controle positif est ce qui rend le negatif lisible : la sonde fonctionne, le module est bien charge, l'auteur est bien reconnu comme bot. La difference tient au vocabulaire du refus, pas au dispositif.

La review de #14535 ne fait pas qu'omettre un mot-cle : elle se termine sur un refus d'approbation net et pose trois demandes numerotees. C'est exactement ce que ~/.claude/CLAUDE.md section B.0 decrit — « un reviewer qui ecrit "je ne peux pas approuver seul sur ce format" bloque » — et le gate rend vert.

Reproduction

import importlib.util, json, subprocess
spec = importlib.util.spec_from_file_location("nits", "scripts/check_unaddressed_nits.py")
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)

def reviews(pr):
    out = subprocess.run(["gh","pr","view",str(pr),"--repo","jsboige/CoursIA",
                          "--json","reviews"], capture_output=True, text=True).stdout
    return json.loads(out)["reviews"]

for pr in (14535, 14493):                       # negatif, puis positif
    for r in reviews(pr):
        print(pr, r["author"]["login"], repr(mod.classify(r["author"]["login"], r["body"] or "")))
# 14535 clusterManager-Myia None
# 14493 clusterManager-Myia 'BOT-CONCERN'

Ce qui manque au vocabulaire

Les 21 motifs actuels attrapent des etiquettes structurees (COMMENT_WITH_CONCERNS, CHANGES_REQUESTED, STRUCTURAL_ONLY, SCOPE FLAG, 🟡, 🔴, **BLOCKED**...) et quelques tournures d'exigence (il va falloir, a nuancer, avant merge, a changer). Ils n'attrapent aucune formule de refus d'approbation :

  • pas de LGTM / pas de LGTM en l'etat / no LGTM
  • je ne peux pas approuver / cannot approve / not approving
  • ne pas merger en l'etat / do not merge as is
  • sous reserve de en position de verdict final

La discipline que l'ajout doit suivre

Elargir la liste est le remede evident et le piege classique — anti-regression.md le documente deja : un jeu de motifs ecrit a la main se valide par ses faux negatifs, pas par ses hits. Deux exigences, donc :

  1. Chaque motif ajoute arrive avec un test qui echoue sans lui (controle par mutation, sur le modele du test_..._ce1_mutation_desactivee_fp_rougit de fix(guards,#13083): Position I' -- 'avant merge' en tete de corps neutralise si pas de verbe actionnel #14538). Un motif sans son test est un motif qu'un refactor retirera sans faire rougir quoi que ce soit.
  2. La direction de l'erreur se mesure des deux cotes. Ce ticket documente un sous-blocage ; le ticket frere check_unaddressed_nits : un BLOCAGE coordinateur est invisible a l'organe — le symetrique non traite de #11639 #13083 documente une sur-accusation de avant merge. La meme liste porte les deux defauts, et un ajout maladroit echange l'un contre l'autre. Chaque motif ajoute doit venir avec un FP plausible teste comme restant None.

Acceptance

Contexte

Trouve en refusant de merger #14535 malgre rc=0 : la review NanoClaw du 2026-09-03T23:50:16Z posait trois demandes et se terminait sur un refus, et l'organe rendait vert. Le refus a tenu parce que j'avais lu la review ; il n'aurait pas tenu autrement — et c'est precisement ce que B.0 dit qu'on ne doit pas dependre.

Meme discipline de depot que #14550 (prev_guard qui echoue-ouvert en silence) : le defaut est mesure avec son controle positif avant d'etre affirme.

See #13083

Activity

  1. jsboige commented on Sep 4, 2026

    @jsboige
    Owner

    [CLAIMED] #14553 — myia-po-2023:CoursIA — 2026-09-04T19:25Z — paths: scripts/check_unaddressed_nits.py, scripts/tests/test_check_unaddressed_nits.py. PR ouverte concurrente sur le chemin : #14645 (même lane, régions distinctes — CONCERN_MARKERS vs regex persona l.153/l.3163 ; rebase prévu après son merge). Grain: MED/guard.

  2. added a commit that references this issue on Sep 4, 2026
  3. jsboige commented on Sep 4, 2026

    @jsboige
    Owner

    [RELEASED] PR #14649 — lane myia-po-2023:CoursIA — 2026-09-04T16:51:23Z

    Acceptance couverte point par point : #14535 corps reel -> BOT-CONCERN ; controle positif #14493 non-regresse ; 7 motifs x (test emission + FP controle) + 2 controles mutation ; suite 372 passed. Corpus 200 PRs mergees / 1060 items : 2 flips, 0 FP — le second flip (#14484, « je recommande de NE PAS merger en l'etat ») est un rattrapage vrai positif. Piece additionnelle mesuree en implementant : negateur lift anglais « no » requis (LIFT_MARKERS lisait « no LGTM » comme approbation avant meme l'evaluation des marqueurs). « sous reserve de » exclu (positionnel), residuel documente.

  4. added a commit that references this issue on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions