Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions scripts/check_unaddressed_nits.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@
r")"
)

# Sentinelle du marqueur ETIQUETE : une valeur inecrivable en prose, pour que la
# forme « Concern: » soit un marqueur A PART -- relachee en casse et en nombre --
# sans toucher a la sous-chaine « CONCERNS », qui reste case-sensitive.
_CONCERN_LABEL = "\x00concern-label"

CONCERN_MARKERS = (
"COMMENT_WITH_CONCERNS", "CHANGES_REQUESTED", "REQUEST_CHANGES",
"NEEDS_CHANGES", "CONCERNS",
Expand Down Expand Up @@ -308,6 +313,11 @@
BLOCK_VERDICTS = ("**BLOCKED**", "BLOCKED PR")
CONCERN_MARKERS = CONCERN_MARKERS + BLOCK_VERDICTS

# La forme ETIQUETEE (« Concern: », « Concerns :», « **Concern 2 :** ») rejoint
# les marqueurs vivants : elle tolere casse et nombre des deux cotes (user comme
# agents) sans rien devoir a la sous-chaine nue, qui reste case-sensitive.
CONCERN_MARKERS = CONCERN_MARKERS + (_CONCERN_LABEL,)

# Un commentaire qui ANNONCE la levee ou le merge n'est pas un nit — il en est

# Un commentaire qui ANNONCE la levee ou le merge n'est pas un nit — il en est
Expand Down Expand Up @@ -1486,6 +1496,39 @@ def has_live_lift(body: str) -> bool:
return bool(_live_lift_positions(_unaccent(body)))


# Marqueurs reconnus par MOTIF plutot que par sous-chaine. La cle est le marqueur
# du tuple, minuscule et desaccentue (has_live_marker normalise des deux cotes).
#
# « CONCERNS » : la sous-chaine nue ne peut pas devenir insensible a la casse sans
# retourner l'organe contre lui-meme. Mesure sur 588 commentaires (corpus 80 PRs
# ouvertes + 70 mergees, 2026-09-01) : la seule insensibilite a la casse fait
# basculer 6 verdicts None -> BOT-CONCERN, et les 6 sont des narrations de LEVEE
# qui citent le mot en y REPONDANT (« les 2 concerns sont traitees au commit X »,
# « Reponse a la CONCERN empirique »). Les bloquer serait le miroir exact du
# defaut que B.0 traque. On retient donc la forme ETIQUETEE en tete de ligne --
# « Concern: », « concerns :», « **Concern 2 :** », « > CONCERNS : » -- qui est
# une EMISSION et non une mention, tolere la casse et le nombre des deux cotes,
# et laisse muettes les six narrations mesurees.
# Marqueurs dont la casse NE se relache PAS. Deux familles, une raison commune :
# leur variante de casse est plus rare que les mentions qu'elle attraperait.
# - prose : « AVANT merge » est une emphase sur la chronologie dans une
# narration de levee Voie 3, pas une reserve (2 cas mesures sur 588).
# - « CONCERNS » nu : « les 2 concerns sont traitees », « Reponse a la CONCERN
# empirique » sont des REPONSES a une reserve (6 cas mesures). Le relachement
# de casse pour ce mot passe par _CONCERN_LABEL ci-dessous, qui exige la
# forme etiquetee -- donc une emission, pas une mention.
_CASE_SENSITIVE_MARKERS = frozenset({
"avant merge", "avant de merger", "before merge",
"il va falloir", "a nuancer", "à nuancer", "a changer",
"CONCERNS",
})


_WORD_BOUNDED_MARKERS = {
_CONCERN_LABEL: re.compile(r"(?m)^[\s*_#>\-]*concerns?\s*\d*\s*:"),
}


def has_live_marker(body: str, markers: tuple[str, ...]) -> bool:
"""Marqueur present avec au moins une occurrence NON citee.

Expand All @@ -1502,9 +1545,28 @@ def has_live_marker(body: str, markers: tuple[str, ...]) -> bool:
compris), l'occurrence est morte ; le marqueur ne vit que si au moins une
occurrence survit.
"""
normalised = _unaccent(body)
raw = _unaccent(body)
lowered = raw.lower()
for marker in markers:
m = _unaccent(marker)
m = _unaccent(marker).lower()
# La casse ne se relache que sur les JETONS de verdict (identifiants
# ecrits en capitales par convention, dont les variantes de casse sont
# des accidents de frappe). Les marqueurs de PROSE gardent leur forme
# litterale : mesure du 2026-09-01 sur 588 commentaires -- relacher la
# casse de « avant merge » fait basculer 2 verdicts, et les 2 sont des
# narrations de levee Voie 3 (« issue #14030 ouverte AVANT merge »), ou
# la majuscule est une EMPHASE sur la chronologie, pas une reserve.
if marker in _CASE_SENSITIVE_MARKERS:
normalised, m = raw, _unaccent(marker)
else:
normalised = lowered
word_re = _WORD_BOUNDED_MARKERS.get(m)
if word_re is not None:
for hit in word_re.finditer(normalised):
i = hit.start()
if not _is_cited(normalised[max(0, i - 30):i]):
return True
continue
start = 0
while (i := normalised.find(m, start)) != -1:
if not _is_cited(normalised[max(0, i - 30):i]):
Expand Down
62 changes: 62 additions & 0 deletions scripts/tests/test_check_unaddressed_nits.py
Original file line number Diff line number Diff line change
Expand Up @@ -3994,6 +3994,68 @@ def test_13951_concern1_glyphe_severite_avec_rien_de_bloquant_ne_passe_pas():
assert mod._sole_live_concern_is_comment_prefix(body_glyphe) is False
assert mod.classify("jsboige", body_glyphe) == "BOT-CONCERN"
# ---------------------------------------------------------------------------
# Forme ETIQUETEE « Concern: » -- casse et nombre relaches (mandat user
# 2026-09-01). Le user posait ses remarques en francais nu, sans marqueur : ses
# commentaires etaient classes None, donc invisibles au merge-gate. Il propose
# d'adopter « Concern: » ; le present bloc rend cette proposition vraie, pour
# lui ET pour les agents, dont la casse varie aussi.
# ---------------------------------------------------------------------------

def test_concern_label_singulier_toute_casse_est_une_reserve():
for body in (
"Concern: ce travail devrait etre distille dans la serie QC.",
"concern : a distiller dans la serie QC.",
"CONCERN : a distiller dans la serie QC.",
"Concerns: deux points a revoir.",
"**Concern 2 :** le scope ne colle pas.",
"> Concern: revoir le perimetre.",
"Bonjour,\n\nConcern: revoir le perimetre.",
):
assert mod.classify("jsboige", body) == "BOT-CONCERN", body


def test_concern_narration_de_levee_ne_bloque_pas():
"""Les 6 faux positifs mesures le 2026-09-01 sur 588 commentaires reels.

Tous CITENT le mot en REPONDANT a une reserve : les bloquer serait le
miroir exact du defaut que B.0 traque. Seule la forme etiquetee en tete de
ligne est une emission ; « les 2 concerns sont traitees » n'en est pas une.
"""
for body in (
"Levee explicite : les 2 concerns Hermes sont adressee au commit 97e970c6.",
"Reponse a la CONCERN empirique (review jsboige).",
"Les concerns 1, 2 et 3 sont leves au commit b0d5eb59.",
):
assert mod.classify("jsboige", body) is None, body


def test_concern_ne_matche_pas_le_francais_courant():
"""« concerne », « concernant », « concernes » ne sont pas des reserves."""
for body in (
"Ce commit concerne la serie QC.",
"Concernant la serie QC, tout est bon. LGTM",
"Cela ne concerne pas cette PR.",
):
assert mod.classify("jsboige", body) is None, body


def test_jeton_de_verdict_tolere_la_casse():
"""Un agent qui ecrit le jeton en casse mixte emet le meme verdict."""
assert mod.classify("jsboige", "Comment_With_Concerns : deux reserves.") == "BOT-CONCERN"


def test_prose_marker_reste_case_sensitive():
"""« AVANT merge » en emphase narre une levee Voie 3 -- 2 cas mesures.

Relacher la casse de la prose retournerait l'organe contre les levees
qu'il doit reconnaitre.
"""
assert mod.classify(
"jsboige",
"Voie 3 B.0 : issue #14030 ouverte AVANT merge, body amende.",
) is None


# #14130 - Position F : verdict attribue a un tiers sans quote ni crochet.
# Bug fondateur (#14070, 2026-09-01) : 2 des 3 points non leves sur #14070
# etaient les commentaires de diagnostic de la lane elle-meme, qui NOMMAIENT
Expand Down
Loading