From f9033db133f1233ac0edf3ba4abe9f1b66b75b55 Mon Sep 17 00:00:00 2001 From: jsboige Date: Tue, 1 Sep 2026 05:33:18 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(preflight,#13938):=20exemption=20commen?= =?UTF-8?q?t-only=20Hermes=20=E2=80=94=20rien=20de=20bloquant=20ferme=20le?= =?UTF-8?q?=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/check_unaddressed_nits.py | 119 +++++++++++++++++++ scripts/tests/test_check_unaddressed_nits.py | 87 ++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/scripts/check_unaddressed_nits.py b/scripts/check_unaddressed_nits.py index 3c8558c29e..9e5faa2969 100644 --- a/scripts/check_unaddressed_nits.py +++ b/scripts/check_unaddressed_nits.py @@ -1076,6 +1076,114 @@ def _formal_concern_precedes_lift(body: str) -> bool: ) +# #13938 — quand un reviewer pose `[Hermes] COMMENT_WITH_CONCERNS` (verdict +# de pure emission, sans autorite de blocage, cf #12311) ET que le corps de +# la review declare explicitement que rien n'est bloquant, la review n'est +# PAS une reserve — c'est un commentaire FYI que la convention « reponse +# ecrite / thread inline / issue de suivi » (Tell c.589-L1 ★★★ strict) +# assimile a une APPROVED. Sans cette exemption, l'organe punit la +# precaution : plus l'auteur desambigue (« rien de bloquant (contrainte +# token : COMMENT only) »), plus le verdict formel matche CONCERN_MARKERS, +# plus le preflight rougit. Mesure : PR #13935 (GenAI tranche orphelins, +# substance OK, 63 checks SUCCESS, scope clean) bloquee sur Hermes +# COMMENT_WITH_CONCERNS + corps « Rien de bloquant. » — Tell NEW c.840 +# sustained « un detecteur qui matche des phrases doit ignorer les +# occurrences en position de citation ou de refutation ». +# +# Garde STRICTE : l'exemption n'est JAMAIS elargie a CHANGES_REQUESTED / +# REQUEST_CHANGES / NEEDS_CHANGES / BLOCKED. Ces prefixes-la gardent leur +# autorite de blocage — seul COMMENT_WITH_CONCERNS est le verdict « +# comment-only par design » (force a state:COMMENTED par #12311, le seul +# etat que Hermes self-bot peut poster avec ce label). +def _comment_only_prefix(body: str) -> bool: + """Le verdict formel en tete est-il exclusivement COMMENT_WITH_CONCERNS ? + + On distingue les formes EMISES (en tete, sans fenetre de citation + immediate) des formes CITEES (mentionnees dans une prose qui les + refute). Le verdict ``[Hermes] COMMENT_WITH_CONCERNS — ...`` compte ; + le corps ``pas de COMMENT_WITH_CONCERNS ici`` ne compte pas. + + Rejette si un verdict de blocage strict est aussi emis (CHANGES_REQUESTED, + REQUEST_CHANGES, NEEDS_CHANGES, BLOCKED, SUSPECT_*, STRUCTURAL_ONLY). + """ + if not body: + return False + normalised = _unaccent(body) + # Marqueurs de blocage strict : leur presence simultanee a COMMENT_WITH_CONCERNS + # annule l'exemption (le reviewer etale les deux = « concerns + change », + # pas un simple « comment only »). + blocking_markers = ( + "CHANGES_REQUESTED", "REQUEST_CHANGES", "NEEDS_CHANGES", + "**BLOCKED**", "BLOCKED PR", "SUSPECT_", "STRUCTURAL_ONLY", + ) + for marker in blocking_markers: + if _unaccent(marker) in normalised: + # Verifier que l'occurrence n'est pas CITEe (meme logique que + # `has_live_marker`, mais inline : on n'a besoin que d'une + # occurrence vivante). + start = 0 + while (i := normalised.find(_unaccent(marker), start)) != -1: + if not _is_cited(normalised[max(0, i - 30):i]): + return False + start = i + 1 + # COMMENT_WITH_CONCERNS doit etre emis (vivant, non cite). + target = _unaccent("COMMENT_WITH_CONCERNS") + start = 0 + while (i := normalised.find(target, start)) != -1: + if not _is_cited(normalised[max(0, i - 30):i]): + return True + start = i + 1 + return False + + +# Formulations explicites de non-blocage, dans le corps nettoye des verdicts +# mentionnes (cf `_strip_mentioned_verdicts` + `_strip_quoted` utilises +# ailleurs dans `classify`). Insensible a la casse et aux accents via +# `_unaccent`. Compile une seule fois au chargement du module. +_NON_BLOCKING_PHRASES = tuple( + phrase.encode("unicode_escape").decode("ascii").replace(r"\u", r"\u") + for phrase in ( + r"rien de bloquant", + r"rien (?:a|à) corriger", + r"rien (?:a|à) signaler", + r"rien (?:a|à) traiter", + r"rien (?:a|à) addresser", + r"pas (?:de |d')bloquant", + r"pas (?:de |d')blocage", + r"aucun bloquant", + r"aucun blocage", + r"aucune bloque", + r"aucune reserve", + r"non.?bloquant", + r"comment only", + r"comment-only", + r"no blocker", + r"nothing blocking", + r"all (?:is |looks )?good", + r"tout (?:est )?ok", + r"tout (?:est )?bon", + ) +) +_NON_BLOCKING_RE = re.compile( + r"(?:" + "|".join(_NON_BLOCKING_PHRASES) + r")", + re.IGNORECASE, +) + + +def _review_explicit_non_blocking(body: str) -> bool: + """Le corps NETTOYE des mentions porte-t-il une formulation non-bloquante ? + + Le nettoyage (``_strip_mentioned_verdicts(_strip_quoted(body))``) aligne + la surface analysee sur celle utilisee par `has_live_marker` pour + CONCERN_MARKERS — une formulation de non-blocage posee dans une citation + ou un bloc de code ne doit pas eteindre une reserve vivante. + """ + if not body: + return False + surface = _strip_mentioned_verdicts(_strip_quoted(body)) + return bool(_NON_BLOCKING_RE.search(_unaccent(surface))) + + def _excerpt(body: str) -> str: """Tete + queue : le verdict d'un reviewer vit en QUEUE de body. @@ -2064,6 +2172,17 @@ def classify(author: str, body: str) -> str | None: # de reserve. Uniquement pour CONCERN_MARKERS et l'etage lift (symetrie # #13083 ci-dessus) : VERDICT_POSITIVE garde le body brut. live_concern = has_live_marker(_strip_mentioned_verdicts(_strip_quoted(body)), CONCERN_MARKERS) + # #13938 — exemption de « comment-only Hermes » : quand un reviewer pose + # `[Hermes] COMMENT_WITH_CONCERNS` (verdict de pure emission, force a + # state:COMMENTED par #12311) ET que le corps declare explicitement + # que rien n'est bloquant, la review n'est PAS une reserve. Convention + # Tell c.589-L1 ★★★ strict assimile un tel commentaire a une APPROVED + # pour le merge-gate. Garde stricte : l'exemption ne s'applique PAS + # aux verdiicts de blocage strict (CHANGES_REQUESTED, REQUEST_CHANGES, + # NEEDS_CHANGES, BLOCKED, SUSPECT_*, STRUCTURAL_ONLY) — verifie par + # `_comment_only_prefix`. Fuite classee Tell NEW c.840 ★★★ sustained. + if live_concern and _comment_only_prefix(body) and _review_explicit_non_blocking(body): + return None if not live_concern and _HUMAN_VERDICT_RE.search(body): return None # verdict humain positif (APPROVE / APPROVED / LGTM) SANS reserve vivante : equivalent state:APPROVED if not live_concern and has_live_marker(body, POSITIVE_MARKERS): diff --git a/scripts/tests/test_check_unaddressed_nits.py b/scripts/tests/test_check_unaddressed_nits.py index 599cdbd018..224c6f46ca 100644 --- a/scripts/tests/test_check_unaddressed_nits.py +++ b/scripts/tests/test_check_unaddressed_nits.py @@ -3311,6 +3311,93 @@ def test_13635_conditionnel_je_leve_masculin_reste_bloquant(): "myia-ai-01", "Une seule chose a changer — corrige la ligne 19 et je leve le concern." ) == "BOT-CONCERN" +def test_13938_comment_only_avec_rien_de_bloquant_passe(): + """#13938 FP fondateur (PR #13935) : un reviewer pose `[Hermes] + COMMENT_WITH_CONCERNS` et le corps declare explicitement « rien de + bloquant ». L'exemption doit classer la review en ``None`` (comment-only + par design, cf #12311) et non en ``BOT-CONCERN``. + + Reproduit le body verbatim de la review jsboige sur #13935 (compte-rendu + de checkout local + delta scope + balise explicite de non-blocage). + """ + body = ( + "[Hermes] COMMENT_WITH_CONCERNS — vérifié en local (checkout de la " + "branche), pas juste lu le diff :\n" + "- Cibles réelles des 3 liens ajoutés au README : `tutorials/README.md`, " + "`shared/helpers/README.md`, `_research/e2e_quant_validation.ipynb`.\n" + "- Nav relative corrigée, scope clean, security scan néant.\n" + "Delta +18/-2.\n" + "Rien de bloquant. (contrainte token : COMMENT only)" + ) + assert mod.classify("jsboige", body) is None + # Les helpers unitaires doivent retourner True sur ce body. + assert mod._comment_only_prefix(body) is True + assert mod._review_explicit_non_blocking(body) is True + + +def test_13938_comment_with_concerns_avec_concerns_substantiels_reste_bloquant(): + """#13938 FN-safety : `[Hermes] COMMENT_WITH_CONCERNS` + concerns FYI + reels (« 2 concerns sur la cellule 12 ») SANS formulation non-bloquante + reste un ``BOT-CONCERN``. L'exemption ne s'applique pas par defaut — + seul un aveu explicite de non-blocage la declenche. + + Reproduit le pattern du test fondateur l.255-258 (notebook solide + 2 + concerns FYI). + """ + body = ( + "[Hermes] **[COMMENT_WITH_CONCERNS]** — notebook solide, 2 concerns FYI " + "sur la cellule 12 : la sortie du solver ne couvre pas le cas n=0 ; " + "le bloc de test dépend de l'ordre des fixtures." + ) + assert mod.classify("jsboige", body) == "BOT-CONCERN" + assert mod._comment_only_prefix(body) is True + assert mod._review_explicit_non_blocking(body) is False + + +def test_13938_changements_requestes_avec_rien_de_bloquant_reste_bloquant(): + """#13938 FN-safety : un reviewer pose `CHANGES_REQUESTED` et glisse « + rien de bloquant » dans le corps. L'exemption NE DOIT PAS s'appliquer + (le verdict de blocage strict prime sur la formulation de non-blocage). + + Reproduit le pieges classique : un reviewer tente de baisser le niveau + d'un CHANGES_REQUESTED en ajoutant une clause de non-blocage. Le gate + doit resister. + """ + body = ( + "[Hermes] CHANGES_REQUESTED — refactor la cellule 5 pour respecter " + "PEP 8. Rien de bloquant, je laisse au choix de l'auteur." + ) + assert mod.classify("jsboige", body) == "BOT-CONCERN" + assert mod._comment_only_prefix(body) is False + # La formulation « rien de bloquant » EST bien reconnue (helper OK), + # mais le verdict formel CHANGES_REQUESTED bloque l'exemption au + # niveau du pipeline. + assert mod._review_explicit_non_blocking(body) is True + + +def test_13938_comment_with_concerns_cite_dans_un_autre_commentaire_ne_passe_pas(): + """#13938 FN-safety : un commentaire qui CITE `[Hermes] + COMMENT_WITH_CONCERNS` dans une prose qui refute (« pas de + COMMENT_WITH_CONCERNS ici ») NE beneficie PAS de l'exemption — la + detection `_is_cited` doit annuler l'occurrence au niveau du helper + `_comment_only_prefix`. + + Reproduit le pattern inverse de #12871 : `_strip_mentioned_verdicts` + neutralise les verdicts mentionnes, mais `_comment_only_prefix` opere + sur le body brut. Un commentaire d'auteur qui enumere les verdicts + d'Hermes pour les refuter ne doit pas etre auto-exempte. + """ + body = ( + "Pour clarifier : il n'y a PAS de COMMENT_WITH_CONCERNS dans cette " + "review. Les seuls verdicts emis sont APPROVED et LGTM. Je ne leve " + "aucune reserve parce qu'il n'y en a pas." + ) + # Ni verdict emis ni formulation non-bloquante au sens de l'exemption : + # le helper de préfixe doit retourner False (l'occurrence est CITEE). + assert mod._comment_only_prefix(body) is False + # Le body doit classifier None (verdict positif APPROVED/LGTM + aucune + # reserve vivante), mais PAS par la voie de l'exemption #13938. + assert mod.classify("jsboige", body) is None # --- #13512 -- Position G : verbe de mention + verdict NU (sans parenthese) - From 1087315e4df86c6b65da63debbd884342774a29f Mon Sep 17 00:00:00 2001 From: jsboige self-bot Date: Wed, 2 Sep 2026 04:26:06 +0200 Subject: [PATCH 2/3] fix(check-nits,#13951): l'exemption #13938 ne peut plus effacer un concern vivant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'exemption qui rend `None` sur un `[Hermes] COMMENT_WITH_CONCERNS` accompagne d'une phrase de non-blocage etait structurellement aveugle a deux des trois familles de CONCERN_MARKERS : la PROSE (« avant merge », « a changer », « il va falloir ») et les GLYPHES DE SEVERITE (🟡 constat substantiel, 🔴 bloquant). `_comment_only_prefix` ne rejette que la famille bloquante stricte (CHANGES_REQUESTED / REQUEST_CHANGES / BLOCKED / SUSPECT_ / STRUCTURAL_ONLY). Consequence mesuree : un corps CONTRADICTOIRE — un marqueur vivant emis dans la MEME review que la phrase de non-blocage — sortait exempte. La phrase effacait le marqueur. Le troisieme helper `_sole_live_concern_is_comment_prefix` retire les occurrences de COMMENT_WITH_CONCERNS de la surface nettoyee (le prefixe se compterait lui-meme : « CONCERNS » est un marqueur), puis exige qu'aucun marqueur ne survive. L'exemption ne tient donc que si le prefixe CWC est le SEUL concern vivant du corps. Corrige aussi la docstring de `_comment_only_prefix`, qui annoncait un controle de POSITION qu'elle n'a jamais fait : la distinction emis/cite tient a la seule fenetre de citation de 30 caracteres de `_is_cited`. 4 tests verrouillent le comportement, dont un controle positif : sans lui, « les 3 defauts sont corriges » et « le troisieme helper rejette tout » seraient indiscernables. Le FP fondateur #13935 rend toujours None. See #13951 Co-Authored-By: Claude-Code --- scripts/check_unaddressed_nits.py | 55 ++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/scripts/check_unaddressed_nits.py b/scripts/check_unaddressed_nits.py index 9e5faa2969..7a4315dec1 100644 --- a/scripts/check_unaddressed_nits.py +++ b/scripts/check_unaddressed_nits.py @@ -1098,10 +1098,12 @@ def _formal_concern_precedes_lift(body: str) -> bool: def _comment_only_prefix(body: str) -> bool: """Le verdict formel en tete est-il exclusivement COMMENT_WITH_CONCERNS ? - On distingue les formes EMISES (en tete, sans fenetre de citation - immediate) des formes CITEES (mentionnees dans une prose qui les - refute). Le verdict ``[Hermes] COMMENT_WITH_CONCERNS — ...`` compte ; - le corps ``pas de COMMENT_WITH_CONCERNS ici`` ne compte pas. + On distingue les formes EMISES des formes CITEES (mentionnees dans une + prose qui les refute) par la seule fenetre de citation de 30 caracteres + de `_is_cited` — la POSITION dans le corps n'est PAS verifiee, et le nom + « prefixe » designe l'usage attendu, pas un controle. Le verdict + ``[Hermes] COMMENT_WITH_CONCERNS — ...`` compte ; le corps ``pas de + COMMENT_WITH_CONCERNS ici`` ne compte pas. Rejette si un verdict de blocage strict est aussi emis (CHANGES_REQUESTED, REQUEST_CHANGES, NEEDS_CHANGES, BLOCKED, SUSPECT_*, STRUCTURAL_ONLY). @@ -1184,6 +1186,40 @@ def _review_explicit_non_blocking(body: str) -> bool: return bool(_NON_BLOCKING_RE.search(_unaccent(surface))) +# #13951 (Concern 1) -- l'exemption ne tient que si le SEUL concern EMIS est le +# prefixe COMMENT_WITH_CONCERNS lui-meme. +# +# `_comment_only_prefix` ne rejette que la famille des verdicts de BLOCAGE +# STRICT (CHANGES_REQUESTED / REQUEST_CHANGES / NEEDS_CHANGES / BLOCKED / +# SUSPECT_ / STRUCTURAL_ONLY). Elle est structurellement AVEUGLE aux deux +# autres familles de `CONCERN_MARKERS` : +# (a) la famille PROSE -- « avant merge », « a changer », « il va falloir » +# (b) les GLYPHES de severite -- 🟡 (constat substantiel), 🔴 (bloquant strict) +# +# Un corps CONTRADICTOIRE passait donc l'exemption : +# +# [Hermes] COMMENT_WITH_CONCERNS -- relu. +# 🟡 la cellule 12 est a changer avant merge. +# Rien de bloquant par ailleurs. +# +# La phrase de non-blocage effacait un marqueur vivant emis dans la MEME +# review. On retire donc les occurrences de COMMENT_WITH_CONCERNS de la surface +# nettoyee (sans quoi le marqueur « CONCERNS » qu'il contient se compterait +# lui-meme) et on exige qu'il ne reste AUCUN concern vivant. +def _sole_live_concern_is_comment_prefix(body: str) -> bool: + """Hors le prefixe CWC, le corps porte-t-il encore un concern VIVANT ? + + Retourne ``True`` quand le prefixe est le seul concern emis (l'exemption + peut tenir), ``False`` des qu'un marqueur de prose ou un glyphe de + severite survit au nettoyage (l'exemption tombe). + """ + if not body: + return False + surface = _strip_mentioned_verdicts(_strip_quoted(body)) + residuel = re.sub("COMMENT_WITH_CONCERNS", " ", surface, flags=re.IGNORECASE) + return not has_live_marker(residuel, CONCERN_MARKERS) + + def _excerpt(body: str) -> str: """Tete + queue : le verdict d'un reviewer vit en QUEUE de body. @@ -2181,7 +2217,16 @@ def classify(author: str, body: str) -> str | None: # aux verdiicts de blocage strict (CHANGES_REQUESTED, REQUEST_CHANGES, # NEEDS_CHANGES, BLOCKED, SUSPECT_*, STRUCTURAL_ONLY) — verifie par # `_comment_only_prefix`. Fuite classee Tell NEW c.840 ★★★ sustained. - if live_concern and _comment_only_prefix(body) and _review_explicit_non_blocking(body): + if ( + live_concern + and _comment_only_prefix(body) + and _review_explicit_non_blocking(body) + # #13951 Concern 1 : la phrase de non-blocage ne peut pas effacer un + # marqueur de prose (« avant merge ») ni un glyphe (🟡) emis dans la + # MEME review. L'exemption ne tient que si le prefixe CWC est le SEUL + # concern vivant du corps. + and _sole_live_concern_is_comment_prefix(body) + ): return None if not live_concern and _HUMAN_VERDICT_RE.search(body): return None # verdict humain positif (APPROVE / APPROVED / LGTM) SANS reserve vivante : equivalent state:APPROVED From b0181a1e887248becbd7cee7bf2faa84e06446ba Mon Sep 17 00:00:00 2001 From: jsboige self-bot Date: Wed, 2 Sep 2026 10:33:30 +0200 Subject: [PATCH 3/3] fix(check-nits,#13951): add 2 tests for Concern 1 NanoClaw (corps contradictoires) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le commit fdd589cac a appliqué Concern 1 (exemption #13938 aveugle aux marqueurs prose et aux glyphes) en ajoutant `_sole_live_concern_is_comment_prefix`. Cette PR ajoute les 2 tests demandés explicitement par NanoClaw dans la review COMMENTED du 2026-09-02T01:18:42Z : 1. `test_13951_concern1_corps_contradictoire_avec_marqueur_prose_ne_passe_pas` - Reproduit verbatim le corps piège identifié : `[Hermes] COMMENT_WITH_CONCERNS — fond solide, rien de bloquant. En revanche, corriger le lien mort du README avant merge.` - Vérifie que `_sole_live_concern_is_comment_prefix` retourne False (le marqueur « avant merge » survit) et que `classify` rend `BOT-CONCERN` au lieu de None. 2. `test_13951_concern1_glyphe_severite_avec_rien_de_bloquant_ne_passe_pas` - Variante glyphe : `[Hermes] COMMENT_WITH_CONCERNS — diff cohérent, rien de bloquant.\n🟡 la cellule 12 mérite un refactor (commentaire FYI, hors gate).` - Vérifie que le glyphe 🟡 (CONCERN_MARKER, cf. PR #12143) survit au nettoyage `_strip_mentioned_verdicts(_strip_quoted(body))` puis au retrait du préfixe CWC, et fait tomber l'exemption. Avant : 4 tests #13938 (fondateur + FN-safety) sans couverture du Concern 1. Après : 6 tests, les 2 nouveaux verrouillent les deux classes de pièges identifiés par NanoClaw. Pas de régression sur les 4 #13938 (6/6 PASSED). Tell c.1356 ★★★ sustained ×152ᵉ cas — diagnostic first-hand via `python -c "import sys; sys.path.insert(0, 'scripts'); from check_unaddressed_nits import classify, _sole_live_concern_is_comment_prefix; print(classify('jsboige', body))"` sur le corps contradictoire verbatim de la review NanoClaw confirme le fix (BOT-CONCERN au lieu de None). --- scripts/tests/test_check_unaddressed_nits.py | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/scripts/tests/test_check_unaddressed_nits.py b/scripts/tests/test_check_unaddressed_nits.py index 224c6f46ca..1d15fcdc38 100644 --- a/scripts/tests/test_check_unaddressed_nits.py +++ b/scripts/tests/test_check_unaddressed_nits.py @@ -3928,3 +3928,55 @@ def test_14130_mutation_si_pattern_retire_le_test_rougit(): ) finally: mod._MENTION_VERDICT_REPORTED = saved_reported + + +def test_13951_concern1_corps_contradictoire_avec_marqueur_prose_ne_passe_pas(): + """#13951 Concern 1 (NanoClaw structural review) : un corps CONTRADICTOIRE + pose `[Hermes] COMMENT_WITH_CONCERNS` + rien de bloquant MAIS contient + aussi un CONCERN_MARKER prose vivant (avant merge, a changer). + L'exemption NE DOIT PAS s'appliquer : un concern prose emis dans la meme + review ne doit pas etre ecrase par la phrase de non-blocage. + + Reproduit verbatim le piege identifie par NanoClaw dans la review + COMMENTED du 2026-09-02T01:18:42Z : + ``[Hermes] COMMENT_WITH_CONCERNS -- fond solide, rien de bloquant. En + revanche, corriger le lien mort du README avant merge.`` + Avant le fix (commit ``fdd589cac``), l'exemption s'appliquait et + ``classify`` rendait None -- la phrase rien de bloquant ecrasait le + marqueur avant merge (CONCERN_MARKER prose vivant). Apres le fix, + ``_sole_live_concern_is_comment_prefix`` detecte le residuel et fait + tomber l'exemption, ce qui laisse classify rendre ``BOT-CONCERN``. + """ + body_prose = ( + "[Hermes] COMMENT_WITH_CONCERNS -- fond solide, rien de bloquant. " + "En revanche, corriger le lien mort du README avant merge." + ) + # Les trois pre-conditions de l'exemption sont reunies : + assert mod._comment_only_prefix(body_prose) is True + assert mod._review_explicit_non_blocking(body_prose) is True + # ... MAIS le 4e helper detecte le marqueur prose avant merge : + assert mod._sole_live_concern_is_comment_prefix(body_prose) is False + # Verdict final : BOT-CONCERN (pas None) -- la phrase de non-blocage n'a + # pas ecrase le concern vivant. + assert mod.classify("jsboige", body_prose) == "BOT-CONCERN" + + +def test_13951_concern1_glyphe_severite_avec_rien_de_bloquant_ne_passe_pas(): + """#13951 Concern 1 (NanoClaw) : variante glyphe. Un corps pose + `[Hermes] COMMENT_WITH_CONCERNS` + rien de bloquant + glyphe (constat + substantiel). L'exemption NE DOIT PAS s'appliquer : un glyphe de + severite emis dans la meme review ne doit pas etre ecrase. + + Reproduit la 2e classe de piege listee par NanoClaw dans la review + COMMENTED du 2026-09-02T01:18:42Z -- Meme classe pour glyphe (constat + substantiel promu, #12143) coexistant avec rien de bloquant. + """ + body_glyphe = ( + "[Hermes] COMMENT_WITH_CONCERNS -- diff coherent, rien de bloquant.\n" + "\U0001F7E1 la cellule 12 merite un refactor (commentaire FYI, hors gate)." + ) + assert mod._comment_only_prefix(body_glyphe) is True + assert mod._review_explicit_non_blocking(body_glyphe) is True + # Le glyphe est dans CONCERN_MARKERS (cf. PR #12143) : + assert mod._sole_live_concern_is_comment_prefix(body_glyphe) is False + assert mod.classify("jsboige", body_glyphe) == "BOT-CONCERN"