Skip to content

feat(ci,#19101): reveiller la branche credited-examples de exercises-advisory (balayage post-mortem) - #19215

Merged
myia-ai-01 merged 3 commits into
mainfrom
fix/19101-credited-sweep
Oct 5, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
fix/19101-credited-sweep

Conversation

@jsboige

@jsboige jsboige commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2026:CoursIA — prev: MED/guard #19214

See #19101 — la branche « exemples credites » de exercises-advisory.yml est reveillee. Option 1 (balayage post-mortem), pas l'option 2.

Quoi: un balayage post-mortem (credited_examples_sweep.py) rejoue le diff des exemples credites pour chaque PR mergee de la fenetre et pose le label credited-examples-lost -- la branche posee par #18761 etait dormante sous schedule.
Preuve: python -m pytest scripts/tests/test_credited_examples_sweep.py -> 17 passed (3 mutants falsifies) ; mesure 24 h sur main : 39 PRs, 0 perte, 0 erreur de diff, 2 renommes nommes non mesures.
Perimetre: scripts/notebook_tools/credited_examples_sweep.py (nouveau), son test, et une etape de plus dans exercises-advisory.yml. Hors scope : l'option 2 (declencheur pull_request leger), toute re-introduction de clone par PR.

Le defaut

La branche --base + --pr-body-file posee par #18761 etait dormante. Le diff des exemples credites exige les deux :

if [ -n "${BASE:-}" ] && [ -n "${PR_BODY_FILE:-}" ] && [ -s "$PR_BODY_FILE" ]; then
  ... --base "$BASE" --pr-body-file "$PR_BODY_FILE"     # mesure le diff
else
  ... (pas de --base)                                    # diff SKIPPE

Or sous schedule — le seul declencheur qui subsiste apres la tranche 1 de #12817 — il n'y a pas de contexte PR : PR_NUMBER est vide, donc PR_BODY_FILE reste vide, donc la condition est fausse et la branche else est prise. BASE etait bien resolu (fenetre nocturne de 24 h) mais ne servait a rien : le diff n'etait jamais mesure, et le label credited-examples-lost ne pouvait structurellement pas etre pose.

Ce que livre la PR

scripts/notebook_tools/credited_examples_sweep.py — un balayage post-mortem : il liste les PRs mergees de la fenetre et rejoue le diff pour chacune, avec sa base et son body. Cable comme nouvelle etape du workflow, apres l'etape existante (qui n'est pas touchee : un sujet par etape).

Aucun pull_request n'est reintroduit sur ce workflow — c'est la contrainte explicite de l'issue, et la motivation d'origine de #12817 (le cout du clone par PR). Le compromis est assume et ecrit dans le module : on detecte les pertes passees, on ne protege pas le merge.

Le piege trouve en mesurant

Premiere mesure sur 24 h : 6 « erreurs de diff » sur 38 PRs. En les instruisant, elles venaient toutes du meme cas : git show <base>:<chemin> sort en 128 parce que le carnet n'existe pas dans la base — il a ete ADDED par la PR. Un carnet neuf ne peut rien perdre.

Ce n'etait pas un detail cosmetique : credited_diff_errors > 0 bloque la pose du label (#18761, garde anti-faux-zero). L'erreur fantome empechait donc la mesure reelle des carnets modifies de la meme PR. Le balayage separe maintenant les deux (_ipynb_by_change) et nomme les carnets neufs dans le rapport au lieu de les faire disparaitre.

Mesure (fenetre de 24 h sur main, --json, sans --apply)

La fenetre compte 118 PRs mergees a --limit 1000 (les 100 rendus a --limit 100 sont le plafond, pas le compte -- cf #19209). 39 touchent un .ipynb :

Grandeur Valeur
PRs avec carnets modifies (mesurees) 37
carnets neufs (ADDED, rien a perdre par construction) 6
carnets renommes (RENAMED, declares NON MESURES) 2
pertes non exemptees mesurees 0
PRs a labelliser 0
erreurs de diff 0
replis nommes (merge-base indisponible) 0

Les deux renommes vivent dans les PRs #19153 et #18986 (ICT-Series) : gh pr view --json files n'expose pas leur chemin de base, donc ils ne sont ni mesures, ni tues -- ils figurent au rapport comme non mesurables.

Lecture honnete : un diff en erreur n'est pas un zero mesure (#18761). Le rapport separe donc « pertes non exemptees = 0 » de « carnets non mesurables », et n'affirme jamais une couverture complete que la mesure ne porte pas.

Tests

test_credited_examples_sweep.py : 17 passed, tout hors ligne (reseau et git injectes) :

  • le lot de l'API de recherche au plafond leve au lieu de passer pour un compte (--limit qui rend exactement le plafond n'est pas un compte, cf Picker --belt : la fenetre de 90 j n'en couvre que ~5 jours (le fetch unique plafonne a 400, troncature par les plus anciennes) #19209) ;
  • le label ne se pose que si tous les diffs ont reussi, meme avec des pertes mesurees ;
  • un merge-base indisponible est nomme en repli, pas silencieux ;
  • un body illisible est nomme et la PR est ecartee, jamais comptee comme zero ;
  • un carnet ADDED n'est pas mesure et est nomme ;
  • un carnet RENAMED est declare non mesure (un renommage peut perdre) et ne bloque plus la mesure des carnets modifies de la meme PR ;
  • une PR purement additive apparait dans le rapport (elle n'est pas supprimee du bilan).

Dents verifiees par falsification (3 mutants, 3 echecs nommes, restaure byte-identical) : plafond silencieux, RENAMED re-assimile a MODIFIED, would_label ignorant les erreurs de diff.

Deuxieme tour — review 5411248369 (90b0435f1e)

Deux voies demandees, les deux faites (la premiere repare la mesure, la seconde empeche qu'une mesure emporte le reste).

Tests 27 -> 32, sur un depot git reel (deux commits, renommage effectif) : MODIFIED absent de l'arbre compte depuis le blob ; l'arbre porte une autre version et la tete gagne ; sans head_ref l'arbre sert encore ; un echec est nomme et les autres PR sont mesurees ; un RuntimeError remonte. Falsification : 3 mutants, 3 rouges.

Rejeu --hours 72 (mode rapport, aucun --apply), rc=0 :

Fenetre : 72 h -- 120 PR(s) mergee(s) touchant un .ipynb
  [  -  ] #19153: 3 carnet(s) modifie(s), pertes non exemptees=0, erreurs de diff=0 (1 renomme(s), NON MESURE(S) : base a un autre chemin)
  [  -  ] #18788: 1 carnet(s) modifie(s), pertes non exemptees=0, erreurs de diff=0
RESULT: 2 PR(s) a labelliser, 4 perte(s) non exemptee(s), 0 erreur(s) de diff, 0 repli(s) nomme(s)

0 repli(s) nomme(s) : le repli n'a pas servi — la premiere voie a suffi, la seconde reste en filet.

Suivi RENAMED : issue #19251 (ouverte avant ce merge). Mesurer les renommages demande previousFilename, absent du jeu GraphQL demande — geste, criteres d'acceptation et exigence de tests y sont ecrits.

Residu nomme

L'option 2 (un declencheur pull_request leger, qui protegerait le merge au lieu de constater apres coup) reste ouverte : elle demanderait de lire les blobs via l'API sans clone complet, ce que check_credited_examples ne sait pas faire aujourd'hui (--base est une ref git). Non traitee ici.

🤖 Generated with Claude Code

…advisory (balayage post-mortem)

La branche « exemples credites » posee par #18761 etait **dormante**. Le diff
exige `--base` ET `--pr-body-file` ; sous `schedule` -- seul declencheur qui
subsiste apres la tranche 1 de #12817 -- il n'y a pas de contexte PR, donc
`PR_NUMBER` est vide, donc `PR_BODY_FILE` reste vide, donc le test de
`exercises-advisory.yml` prend la branche sans `--base` et **saute le diff**.
Le label `credited-examples-lost` ne pouvait structurellement pas etre pose.

## Ce que livre la PR

`scripts/notebook_tools/credited_examples_sweep.py` : balayage **post-mortem**
des PRs mergees de la fenetre, rejouant le diff pour chacune avec SA base et
SON body (option 1 de #19101). Cable comme etape du workflow ; l'etape
existante n'est pas touchee.

**Aucun `pull_request` n'est reintroduit** sur ce workflow : c'est la
contrainte explicite de l'issue (le cout du clone par PR est la motivation
d'origine de #12817). Le compromis -- detecter les pertes passees, pas
proteger le merge -- est ecrit dans le module, pas seulement ici.

## Le piege trouve en mesurant

Premiere mesure sur 24 h : **6 « erreurs de diff »** sur 38 PRs. Instruites,
elles venaient toutes du meme cas : `git show <base>:<chemin>` sort en **128**
parce que le carnet n'est pas a ce chemin dans la base.

Ce n'etait pas cosmetique. `credited_diff_errors > 0` **bloque** la pose du
label (#18761) : une erreur structurelle sur un carnet empechait la mesure
reelle des carnets modifies de la meme PR. Un faux positif d'erreur produisait
un faux zero de pertes.

Trois cas separes, qui ne disent pas la meme chose :

  - `ADDED` : neuf, **rien a perdre** par construction (6 des 6 erreurs) ;
  - `RENAMED` : la base est a un **autre chemin**, que `gh pr view --json files`
    n'expose pas (`previousFilename` absent). Non comparable -- et contrairement
    a `ADDED`, un renommage **peut** perdre des exemples : declare NON MESURE,
    jamais « sans perte » ;
  - `DELETED` : exclu, comme le `--diff-filter=d` du workflow.

Les carnets non mesurables sont **nommes** dans le rapport au lieu de
disparaitre : un carnet tu se lirait comme un carnet conforme.

## Mesure

Fenetre de 24 h sur `main`, `--json`, sans `--apply`. Un diff en erreur n'est
PAS un zero mesure (#18761) : le rapport separe « pertes non exemptees = 0 » de
« carnets non mesurables », et n'affirme jamais une couverture que la mesure ne
porte pas.

## Tests

`test_credited_examples_sweep.py` : tout hors ligne (reseau et git injectes).
Le lot de l'API de recherche **au plafond** leve au lieu de passer pour un
compte ; le label ne se pose que si **tous** les diffs ont reussi ; un
`merge-base` indisponible est nomme ; un body illisible est nomme et la PR
ecartee ; un `ADDED` n'est pas mesure ; un `RENAMED` est declare non mesure ET
ne bloque plus les carnets modifies de la meme PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@github-actions github-actions Bot added variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) and removed variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2026:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #19048 (LIGHT/notebook-twins, merge a 2026-10-05T00:37:49Z), #19043 (MED/ledger, merge a 2026-10-05T02:18:38Z), #19191 (MED/guard, merge a 2026-10-05T03:10:09Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2026:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-10-05) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=1 genre=3 cap=2)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=3 cap=2)

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 J'ai rejoué le balayage sur le corpus réel : il plante. Ce point tient le merge.

Commande lancée depuis un clone de main à jour, avec le script de la tête 174d174713, en mode rapport seul :

python scripts/notebook_tools/credited_examples_sweep.py --repo jsboige/CoursIA --repo-dir . --hours 24
  File ".../credited_examples_sweep.py", line 220, in sweep
    result = check([Path(p) for p in paths], base_ref=base, pr_body=body)
  ...
FileNotFoundError: [Errno 2] No such file or directory: 'MyIA.AI.Notebooks\GameTheory\GameTheory-18d-Humour-Banc-Dur-Python.ipynb'

Cause 1 : _ipynb_by_change ne reçoit jamais de changeType. gh pr list --json files ne renvoie que {additions, deletions, path}. Mesure avec gh 2.83.2 sur #19040 :

gh pr view 19040 --json files --jq '.files[0]'
  -> {"additions":2876,"deletions":214,"path":".../GameTheory-18c-Humour-Banc-Python.ipynb"}

Le repli f.get("changeType") or "MODIFIED" classe donc tous les carnets en MODIFIED, et les branches DELETED, ADDED et RENAMED ne s'exécutent jamais en production. #19040 a supprimé GameTheory-18d (GraphQL : DELETED). Le balayage le traite comme modifié, cherche à l'ouvrir et lève une FileNotFoundError. Comme ce n'est pas une RuntimeError, aucun garde ne l'intercepte.

Les 17 tests passent parce qu'ils injectent des dicts qui portent déjà changeType. Aucun ne passe par la vraie forme de gh pr list.

Cause 2 : la tête est lue dans l'arbre de travail, pas à la tête de la PR. check_notebooks accepte un head_ref, et il lit alors le blob avec _read_git_blob (check_pr_exercises.py l.260-261). Le balayage ne le lui passe pas. Le côté « après » est donc le main du moment du balayage, pas la version que la PR a fusionnée. Un carnet supprimé ou renommé depuis fait planter le balayage. Un carnet retouché depuis attribue à la PR les pertes ou les restaurations d'une PR ultérieure.

Cause 3 : le plantage ne laisse aucune trace visible. Le workflow appelle le balayage avec || echo "::warning::credited sweep failed -- nothing labelled". Avec cette forme, un plantage permanent laisse le run vert chaque nuit. C'est exactement la classe de défaut que corrige #19214, de la même lane, sur epic-neglect-sweep (35 jours verts sans rapport). Faire rougir ce pas ne bloque aucun merge : la mesure du body de #19214 (merge_dwell ne surveille que scripts-tests.yml en event=push) vaut aussi pour ce workflow, qui ne tourne qu'en schedule et dispatch.

Ce qui lèverait ce point :

  1. Lire changeType par GraphQL (pullRequest.files.nodes { path changeType }), ou classer les carnets avec git diff --name-status <base>...<merge_commit>.
  2. Passer head_ref (le commit de merge ou headRefOid) à check_notebooks.
  3. Ajouter un test qui passe par la forme réelle de gh pr list --json files (sans changeType) et sur un carnet supprimé.
  4. Ne plus masquer un plantage derrière || echo : laisser l'étape échouer.
  5. Rejouer la mesure de 24 h et donner son résultat.

…c non masque

Reponse a la review CHANGES_REQUESTED du 05/10 (5 conditions) :

1. changeType n'existe pas dans gh pr list --json files (mesure gh 2.83.2
   sur #19040 : {additions, deletions, path} seulement). Les fichiers et
   leur nature viennent maintenant de GraphQL (pullRequest.files.nodes
   {path changeType}), pagines au curseur -- le repli « tout MODIFIED »
   classait GameTheory-18d (supprime par #19040) en modifie et levait
   FileNotFoundError, hors de tout garde.
2. check_notebooks recoit head_ref=headRefOid : le cote « apres » est la
   tete de la PR, pas l'arbre du moment du balayage. Le commit est amene
   par fetch-by-SHA s'il manque (PR squash-mergee) ; inatteignable = erreur
   nommee, PR ecartee -- jamais mesuree contre l'arbre du jour.
3. Tests : 17 -> 27. La forme reelle du lot (sans files/changeType) est
   desormais un fixture ; carnet supprime sans crash + carnets modifies
   de la meme PR toujours mesures ; pagination >100 fichiers ; tete
   inatteignable ; propagation de head_ref au check. Falsification :
   3 mutants (branche DELETED retiree, head_ref non passe, pagination
   coupee) -> chacun cuche par au moins un test ; restore vert 27/27.
4. Le masque || echo "::warning::..." du workflow est retire : un plantage
   permanent laissait le run vert chaque nuit (classe de defaut #19214).
   set -euo pipefail ; sans effet merge (schedule/dispatch seulement).
5. Rejou de la mesure 24 h sur le corpus reel : dans le corps de la PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@jsboige

jsboige commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Reponse point par point a la review — commit 5cdd314bce (17 -> 27 tests, 3 mutants falsifies).

1. changeType par GraphQL. Fait. La requete GRAPHQL_FILES lit pullRequest.files.nodes { path changeType }, paginee au curseur (pr_files, pages de 100 — une PR de carnet peut deplacer plus de 100 fichiers). merged_prs ne demande plus files : --json number,baseRefOid,headRefOid,mergedAt exactement. Le repli « tout MODIFIED » n'existe plus : DELETED ecarte, ADDED/RENAMED separes.

2. head_ref passe au check. Fait. check_notebooks recoit head_ref=headRefOid ; le commit de tete est amene par fetch-by-SHA s'il manque (ensure_commit : probe cat-file -e, git fetch origin <sha>, re-probe). Un head inatteignable = erreur nommee « commit de tete inatteignable -- PR ecartee », la PR n'est JAMAIS mesuree contre l'arbre du jour. Merge-base indisponible = repli nomme en [WARN] (diff contre la base declaree, provenance dite) — comportement conserve.

3. Tests sur la forme reelle + carnet supprime. 17 -> 27 :

  • fixture _pr() = la forme EXACTE de gh pr list (pas de cle files du tout) — le test echouait sur l'ancien schema ou il reussissait a tort ;
  • regression feat(gametheory,#14032): banc humour consolide -- 18c absorbe 18d (ex-GT-28b) #19040 : carnet DELETED + carnets MODIFIES dans la meme PR -> pas de crash, les modifies restent mesures ;
  • pagination : 101 fichiers sur 2 pages, le curseur CUR1 sert a la 2e requete ;
  • ensure_commit : probe court-circuite le fetch / fetch-then-confirm / introuvable -> False ;
  • propagation : le check recoit bien head_ref=headRefOid et le merge-base comme base.
    Falsification : mutant « branche DELETED retiree » -> 2 tests rouges ; mutant « head_ref non passe » -> 1 rouge ; mutant « pagination coupee » -> 1 rouge ; restore -> 27/27 verts.

4. Masque || echo retire. L'etape passe en set -euo pipefail sans masque : un plantage ROUGIT le run (classe de defaut #19214 — 35 jours verts sans rapport). Les pannes ATTENDUES de l'organe (refs absentes, body illisible, plafond) restent nommees en [WARN] avec sortie 0 — le contrat du verdict est preserve. Sans effet merge : ce workflow ne tourne qu'en schedule/dispatch, hors du perimetre merge_dwell (mesure du body de #19214).

5. Rejeu 24 h sur le corpus reel (05/10 ~09:05Z, depuis un worktree de la branche, mode rapport seul) :

RESULT: 0 PR(s) a labelliser, 0 perte(s) non exemptee(s), 0 erreur(s) de diff, 0 repli(s) nomme(s)

39 PRs mergees balaayees (fenetre 04/10 09:05Z -> 05/10 09:05Z), exit 0, aucun repli. Le cas de plantage de la review est balaay proprement : #19040 = 4 carnets modifies mesures, GameTheory-18d (supprime) ecarte sans erreur. 5 PRs purement additives et 2 renommages sont nommes pour ce qu'ils sont (rien a perdre / NON MESURE, base a un autre chemin) au lieu d'etre comptes en erreur.

Residu assume (deja declare dans la PR) : RENAMED reste non mesure — previousFilename est hors du jeu GraphQL demande ; l'ameliorer serait un follow-up, pas un blocage.

🤖 Generated with Claude Code

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Rejeu de l'organe sur le corpus reel, tete 5cdd314bce : la fenetre de 24 h passe, celle de 72 h plante. Ce point tient le merge.

Mesure faite depuis un worktree detache a la tete, en mode rapport seul (sans --apply) :

Rejeu Resultat
pytest scripts/tests/test_credited_examples_sweep.py 27 passed
--hours 24 44 PR mergees, rc=0, 0 a labelliser, 0 erreur. #19040 et le carnet supprime GameTheory-18d passent proprement.
--hours 72 rc=1, FileNotFoundError: MyIA.AI.Notebooks\IIT\ICT-Series\ICT-45-InoculationBifurcation-9B-Python.ipynb

Cause. #18788 (mergee le 03/10) a modifie ICT-45. Ensuite, #19153 (mergee le 05/10) l'a renomme en ICT-42b. Pour #18788, le chemin est donc MODIFIED, et le balayage appelle check_notebooks. Mais cette fonction compte d'abord les exercices avec count_exercises_in_notebook(path) (check_pr_exercises.py:241), qui ouvre le fichier dans l'arbre de travail. Seul le diff des exemples credites, plus bas, lit le blob a head_ref. Le point 2 de la reponse est donc vrai pour le diff, mais pas pour le comptage qui le precede.

Ce n'est pas un cas de 72 h seulement. Il suffit qu'une PR modifie un carnet et qu'une PR suivante, dans la meme fenetre de 24 h, le renomme ou le supprime. Les tranches de renommage #16231 en produisent souvent. Avec set -euo pipefail, c'est maintenant visible (bon geste). Mais un seul cas fait tomber tout le balayage : les autres PR de la fenetre ne sont pas mesurees.

Ce qui leverait ce point (au choix de la lane) :

  1. compter les exercices sur le blob a head_ref quand il est fourni, comme le fait deja le diff ;
  2. ou, dans sweep, attraper l'echec par PR et le nommer (« carnet absent de l'arbre du jour, renomme ou supprime apres merge »), dans la liste errors, sans arreter les autres PR.

Dans les deux cas, ajouter un test : un chemin MODIFIED dans la PR, absent de l'arbre de travail. Puis rejouer --hours 72 et coller la ligne RESULT dans le fil.

Point secondaire, a lever avant merge. La reponse annonce que la mesure des RENAMED « serait un follow-up ». Ce suivi doit avoir un numero d'issue, ouverte avant le merge.

…lisible n'emporte plus le balayage

Review 5411248369, deux voies demandees, les deux faites.

1. `check_notebooks` comptait les exercices sur l'ARBRE DU JOUR
   (`count_exercises_in_notebook(path)`) alors que le diff credite lit deja le
   blob de `head_ref`. Consequence mesuree : #18788 MODIFIE
   ICT-45-InoculationBifurcation-9B, #19153 le RENOMME ensuite -> le chemin est
   MODIFIED mais absent de l'arbre, et le comptage levait un FileNotFoundError
   qui emportait TOUT le balayage (`--hours 72`, rc=1 : les autres PR de la
   fenetre n'etaient pas mesurees). Le comptage porte desormais sur le blob de
   tete, qui existe par construction pour un chemin MODIFIED. La classification
   reste sur le chemin d'origine : `classify_notebook` lit les regles de
   REPERTOIRE, que le fichier temporaire du blob ne porte pas.

   Effet de bord voulu, sur le meme chemin : meme quand le carnet existe dans
   l'arbre, c'est la revision de la PR qui est comptee -- l'ancien code pouvait
   mesurer un arbre different de celui qu'il comparait.

2. Le repli par PR : un echec de `check` est NOMME (« carnet illisible depuis
   l'arbre du jour (FileNotFoundError: ...) -- renomme ou supprime apres
   merge, PR ecartee ») et la suite de la fenetre est mesuree. La portee du
   `except` est etroite (`OSError`, `ValueError`) : un autre type d'echec
   remonte, pour qu'un bug du compteur ne se derobe pas en « carnet renomme ».

Tests : 27 -> 32. Les nouveaux portent sur un depot git REEL (deux commits,
renommage effectif), pas sur un dict injecte : carnet MODIFIED absent de
l'arbre compte depuis le blob (3 exercices) ; l'arbre porte une autre version
(1 exercice) et la tete gagne ; sans `head_ref` l'arbre sert encore
(retro-compatibilite) ; un `check` qui leve est nomme et les autres PR sont
mesurees ; un `RuntimeError` remonte au lieu d'etre absorbe.

Falsification : 3 mutants, 3 rouges -- comptage remis sur l'arbre (2 tests),
repli par PR retire (1), `except` elargi a `Exception` (1). Source restauree,
32 passed.

Suivi RENAMED : issue #19251 ouverte avant merge (mesurer les renommages,
`previousFilename` absent du jeu GraphQL).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@jsboige

jsboige commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la review 5411248369 — commit 90b0435f1e. Les deux voies proposées sont faites (elles ne se remplacent pas : la première répare la mesure, la seconde empêche qu'une mesure répare tout le reste).

Voie 1 — le comptage lit la révision de la PR. check_notebooks comptait les exercices sur l'arbre du jour (count_exercises_in_notebook(path)) alors que le diff crédité lit déjà le blob de head_ref. Le comptage porte maintenant sur ce blob, qui existe par construction pour un chemin MODIFIED. Point de détail qui a son importance : la classification reste sur le chemin d'origine, parce que classify_notebook lit des règles de répertoire (IIT/, groupe-, _) que le fichier temporaire du blob ne porte pas — classifier sur le blob aurait reclassé des carnets en archive/tooling sur leur seul préfixe.

Effet de bord voulu, sur le même chemin, et épinglé par un test : même quand le carnet existe dans l'arbre, c'est la révision de la PR qui est comptée. L'ancien code pouvait compter un arbre différent de celui qu'il comparait.

Voie 2 — le repli par PR. Un échec de check est nommé et la fenêtre continue : #18788: carnet illisible depuis l'arbre du jour (FileNotFoundError: ...) -- renomme ou supprime apres merge, PR ecartee, dans la liste errors (donc compté dans la ligne RESULT). La portée du except est étroite (OSError, ValueError) : un RuntimeError remonte au lieu d'être absorbé — un mutant élargi le prouve —, pour qu'un bug du compteur ne se déguise pas en « carnet renommé ».

Tests : 27 → 32. Les nouveaux portent sur un dépôt git réel (deux commits, renommage effectif), pas sur un dict injecté :

  1. carnet MODIFIED absent de l'arbre (le cas feat(notebook-python,#18086): ICT-45 volet J-lens étendu au Qwen3.5-27B (4e échelle, lens publié + organe --offload) #18788→rename(#16231): ICT-45 -> ICT-42b -- accrétion de la famille InoculationBifurcation (chantier C) #19153) → compté depuis le blob, 3 exercices, aucune exception ;
  2. l'arbre porte une autre version (1 exercice) et la tête gagne (3) ;
  3. sans head_ref, l'arbre sert encore (rétro-compatibilité du CLI local) ;
  4. un check qui lève est nommé, les autres PR de la fenêtre sont mesurées ;
  5. un RuntimeError remonte au lieu d'être absorbé.

Falsification : 3 mutants, 3 rouges — comptage remis sur l'arbre (2 tests), repli par PR retiré (1), except élargi à Exception (1). Source restaurée, 32 passed.

Rejeu demandé, --hours 72, mode rapport (aucun --apply), depuis la tête 90b0435f1e :

Fenetre : 72 h -- 120 PR(s) mergee(s) touchant un .ipynb
  [  -  ] #19153: 3 carnet(s) modifie(s), pertes non exemptees=0, erreurs de diff=0 (1 renomme(s), NON MESURE(S) : base a un autre chemin)
  [  -  ] #18788: 1 carnet(s) modifie(s), pertes non exemptees=0, erreurs de diff=0
RESULT: 2 PR(s) a labelliser, 4 perte(s) non exemptee(s), 0 erreur(s) de diff, 0 repli(s) nomme(s)

Les deux PR du cas qui plantait sont mesurées : #18788 (le carnet modifié) passe, #19153 (le renommage) nomme son renommage au lieu de le taire. 0 repli(s) nomme(s) : le repli par PR n'a pas servi — la voie 1 a suffi, et la voie 2 reste en filet. Les 6 renommages de la fenêtre sont déclarés NON MESURÉS comme avant (c'est l'objet du suivi ci-dessous, pas un repli).

Suivi RENAMED — issue #19251, ouverte avant ce merge : previousFilename n'est pas dans le jeu GraphQL demandé, donc un renommage qui perd un exemple crédité n'est pas diffable ; l'issue porte le geste (demander le champ), les critères d'acceptation et l'exigence de tests sur un vrai renommage.

🤖 Generated with Claude Code

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je lève ma review CHANGES_REQUESTED 5411248369 (07:29Z). Je l'ai vérifiée à la tête 90b0435f1e, depuis un worktree détaché de cette tête.

  • scripts/tests/test_credited_examples_sweep.py : 32 passed (1,67 s).
  • Rejeu en mode rapport sur une fenêtre plus large que celle de l'auteur (--hours 96 contre 72) : RESULT: 2 PR(s) a labelliser, 4 perte(s) non exemptee(s), 0 erreur(s) de diff, 0 repli(s) nomme(s), rc=0. Il n'y a ni FileNotFoundError, ni repli par PR déclenché.
  • Le cas de ma review (un carnet modifié puis renommé par une PR suivante, lu dans l'arbre de travail) ne plante plus : le comptage lit le blob de head_ref.

Le suivi du renommage est porté par #19251, ouvert avant le merge, et livré en #19261, empilée sur cette PR.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #19215 (feat(ci,#19101): reveiller la branche credited-examples de exercises-advisory (balayage post-mortem)) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@jsboige

jsboige commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-ai-01:CoursIA
pr: 19215
head: 90b0435
complete: true
body: read
comments-reviewed: 6
reviews-reviewed: 3
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 88234997465dc70acd983df7b04474545f6bd978f0fced9e148218f5e279b2b5
diff-files: 4
diff-additions: 958
diff-deletions: 1
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19215
organ-rc: 0
[/ADJOINT PREFLIGHT]

Dossier tiers (ai-01, lane distincte de l'autrice po-2026:CoursIA). Lu à la tête 90b0435f1e : body, tous les commentaires, toutes les reviews, diff (4 fichiers : workflow exercises-advisory.yml, check_pr_exercises.py, credited_examples_sweep.py et ses tests).

@myia-ai-01
myia-ai-01 merged commit dec5d91 into main Oct 5, 2026
25 of 35 checks passed
jsboige added a commit that referenced this pull request Oct 5, 2026
… after #19215 squash-merge)

# Conflicts:
#	scripts/notebook_tools/credited_examples_sweep.py
#	scripts/tests/test_credited_examples_sweep.py
myia-ai-01 pushed a commit that referenced this pull request Oct 5, 2026
…d (previous_filename par REST) (#19261)

* feat(ci,#19101): reveiller la branche credited-examples de exercises-advisory (balayage post-mortem)

La branche « exemples credites » posee par #18761 etait **dormante**. Le diff
exige `--base` ET `--pr-body-file` ; sous `schedule` -- seul declencheur qui
subsiste apres la tranche 1 de #12817 -- il n'y a pas de contexte PR, donc
`PR_NUMBER` est vide, donc `PR_BODY_FILE` reste vide, donc le test de
`exercises-advisory.yml` prend la branche sans `--base` et **saute le diff**.
Le label `credited-examples-lost` ne pouvait structurellement pas etre pose.

## Ce que livre la PR

`scripts/notebook_tools/credited_examples_sweep.py` : balayage **post-mortem**
des PRs mergees de la fenetre, rejouant le diff pour chacune avec SA base et
SON body (option 1 de #19101). Cable comme etape du workflow ; l'etape
existante n'est pas touchee.

**Aucun `pull_request` n'est reintroduit** sur ce workflow : c'est la
contrainte explicite de l'issue (le cout du clone par PR est la motivation
d'origine de #12817). Le compromis -- detecter les pertes passees, pas
proteger le merge -- est ecrit dans le module, pas seulement ici.

## Le piege trouve en mesurant

Premiere mesure sur 24 h : **6 « erreurs de diff »** sur 38 PRs. Instruites,
elles venaient toutes du meme cas : `git show <base>:<chemin>` sort en **128**
parce que le carnet n'est pas a ce chemin dans la base.

Ce n'etait pas cosmetique. `credited_diff_errors > 0` **bloque** la pose du
label (#18761) : une erreur structurelle sur un carnet empechait la mesure
reelle des carnets modifies de la meme PR. Un faux positif d'erreur produisait
un faux zero de pertes.

Trois cas separes, qui ne disent pas la meme chose :

  - `ADDED` : neuf, **rien a perdre** par construction (6 des 6 erreurs) ;
  - `RENAMED` : la base est a un **autre chemin**, que `gh pr view --json files`
    n'expose pas (`previousFilename` absent). Non comparable -- et contrairement
    a `ADDED`, un renommage **peut** perdre des exemples : declare NON MESURE,
    jamais « sans perte » ;
  - `DELETED` : exclu, comme le `--diff-filter=d` du workflow.

Les carnets non mesurables sont **nommes** dans le rapport au lieu de
disparaitre : un carnet tu se lirait comme un carnet conforme.

## Mesure

Fenetre de 24 h sur `main`, `--json`, sans `--apply`. Un diff en erreur n'est
PAS un zero mesure (#18761) : le rapport separe « pertes non exemptees = 0 » de
« carnets non mesurables », et n'affirme jamais une couverture que la mesure ne
porte pas.

## Tests

`test_credited_examples_sweep.py` : tout hors ligne (reseau et git injectes).
Le lot de l'API de recherche **au plafond** leve au lieu de passer pour un
compte ; le label ne se pose que si **tous** les diffs ont reussi ; un
`merge-base` indisponible est nomme ; un body illisible est nomme et la PR
ecartee ; un `ADDED` n'est pas mesure ; un `RENAMED` est declare non mesure ET
ne bloque plus les carnets modifies de la meme PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(ci,#19101): sweep credited — changeType GraphQL, tete de PR, echec non masque

Reponse a la review CHANGES_REQUESTED du 05/10 (5 conditions) :

1. changeType n'existe pas dans gh pr list --json files (mesure gh 2.83.2
   sur #19040 : {additions, deletions, path} seulement). Les fichiers et
   leur nature viennent maintenant de GraphQL (pullRequest.files.nodes
   {path changeType}), pagines au curseur -- le repli « tout MODIFIED »
   classait GameTheory-18d (supprime par #19040) en modifie et levait
   FileNotFoundError, hors de tout garde.
2. check_notebooks recoit head_ref=headRefOid : le cote « apres » est la
   tete de la PR, pas l'arbre du moment du balayage. Le commit est amene
   par fetch-by-SHA s'il manque (PR squash-mergee) ; inatteignable = erreur
   nommee, PR ecartee -- jamais mesuree contre l'arbre du jour.
3. Tests : 17 -> 27. La forme reelle du lot (sans files/changeType) est
   desormais un fixture ; carnet supprime sans crash + carnets modifies
   de la meme PR toujours mesures ; pagination >100 fichiers ; tete
   inatteignable ; propagation de head_ref au check. Falsification :
   3 mutants (branche DELETED retiree, head_ref non passe, pagination
   coupee) -> chacun cuche par au moins un test ; restore vert 27/27.
4. Le masque || echo "::warning::..." du workflow est retire : un plantage
   permanent laissait le run vert chaque nuit (classe de defaut #19214).
   set -euo pipefail ; sans effet merge (schedule/dispatch seulement).
5. Rejou de la mesure 24 h sur le corpus reel : dans le corps de la PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(ci,#19215): le comptage lit la revision de la PR, et un carnet illisible n'emporte plus le balayage

Review 5411248369, deux voies demandees, les deux faites.

1. `check_notebooks` comptait les exercices sur l'ARBRE DU JOUR
   (`count_exercises_in_notebook(path)`) alors que le diff credite lit deja le
   blob de `head_ref`. Consequence mesuree : #18788 MODIFIE
   ICT-45-InoculationBifurcation-9B, #19153 le RENOMME ensuite -> le chemin est
   MODIFIED mais absent de l'arbre, et le comptage levait un FileNotFoundError
   qui emportait TOUT le balayage (`--hours 72`, rc=1 : les autres PR de la
   fenetre n'etaient pas mesurees). Le comptage porte desormais sur le blob de
   tete, qui existe par construction pour un chemin MODIFIED. La classification
   reste sur le chemin d'origine : `classify_notebook` lit les regles de
   REPERTOIRE, que le fichier temporaire du blob ne porte pas.

   Effet de bord voulu, sur le meme chemin : meme quand le carnet existe dans
   l'arbre, c'est la revision de la PR qui est comptee -- l'ancien code pouvait
   mesurer un arbre different de celui qu'il comparait.

2. Le repli par PR : un echec de `check` est NOMME (« carnet illisible depuis
   l'arbre du jour (FileNotFoundError: ...) -- renomme ou supprime apres
   merge, PR ecartee ») et la suite de la fenetre est mesuree. La portee du
   `except` est etroite (`OSError`, `ValueError`) : un autre type d'echec
   remonte, pour qu'un bug du compteur ne se derobe pas en « carnet renomme ».

Tests : 27 -> 32. Les nouveaux portent sur un depot git REEL (deux commits,
renommage effectif), pas sur un dict injecte : carnet MODIFIED absent de
l'arbre compte depuis le blob (3 exercices) ; l'arbre porte une autre version
(1 exercice) et la tete gagne ; sans `head_ref` l'arbre sert encore
(retro-compatibilite) ; un `check` qui leve est nomme et les autres PR sont
mesurees ; un `RuntimeError` remonte au lieu d'etre absorbe.

Falsification : 3 mutants, 3 rouges -- comptage remis sur l'arbre (2 tests),
repli par PR retire (1), `except` elargi a `Exception` (1). Source restauree,
32 passed.

Suivi RENAMED : issue #19251 ouverte avant merge (mesurer les renommages,
`previousFilename` absent du jeu GraphQL).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(ci,#19251): mesurer les carnets RENAMED dans le balayage credited

Le balayage post-mortem des exemples credites (#19101) declarait tout carnet
RENAMED « NON MESURE » faute de connaitre son chemin de base : `previousFilename`
n'etait pas demande au jeu GraphQL. Un `git mv` suivi d'une edition pouvait donc
perdre un exemple credite sans que rien ne le voie.

Le champ est desormais demande (`nodes { path changeType previousFilename }`).
Quand il est present, le renommage est MESURE comme un MODIFIED : le diff
credite lit `base:previousFilename` contre `head:path`. La correspondance passe
par `base_path_of`, consultee en POSIX -- GraphQL rend des `/` et `str(Path)`
des `\` sous Windows : sans la normalisation, la correspondance raterait en
silence et le renommage redeviendrait non mesure (mutant M3).

Quand `previousFilename` manque (renommage sous un seuil de similarite, ou
reponse d'API degradee), le carnet reste nomme NON MESURE -- et surtout, sans
correspondance la base est absente a ce chemin : le diff est en ERREUR, que
#18761 refuse de convertir en label. Jamais un zero silencieux.

Tests 32 -> 37 : le renommage mesure (un exemple credite perdu au `git mv` est
vu), le renommage sans perte (pas de faux positif), et l'absence de zero
silencieux sont epingles sur un depot git REEL ; le contrat de la carte en plus.
Falsification : 4 mutants, 4 rouges -- mapping ignore (M1), `previousFilename`
ignore (M2), cle non normalisee (doublure du bug Windows, M3), diff en erreur
converti en zero silencieux (M4) ; source restauree, 37 passed.

Au passage, les appels `subprocess` des tests touchés portent `encoding="utf-8",
errors="replace"` (garde #13140/#12811 : un hôte cp1252 leve sur un payload UTF-8).

Closes #19251

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(ci,#19251): le chemin de base d'un renommage est cote REST, pas GraphQL

Correction du commit precedent, dont l'hypothese etait fausse et que le rejeu
`--hours 72` a demasquee : `previousFilename` N'EXISTE PAS sur le type GraphQL
`PullRequestChangedFile`. Mesure, serveur :

    Field 'previousFilename' doesn't exist on type 'PullRequestChangedFile'

Ses champs sont `additions, changeType, deletions, path, viewerViewedState`
(verifie par introspection). Demander le champ faisait echouer TOUTES les
lectures de fichiers : `--hours 72` rendait « 0 PR mesuree, 309 replis nommes »
au lieu des 2 PR / 4 pertes de la veille -- une regression, pas un progres.

Le classement reste donc en GraphQL (`changeType`), et le chemin de base est lu
par une SECONDE source, REST : `pulls/{n}/files`, champ `previous_filename`
(verifie sur #19153 : ICT-45 -> ICT-42b). Cette passe n'est faite que si la PR
porte au moins un renommage, et son echec laisse le renommage NON MESURE
(`previousFilename` rendu `""`) au lieu de le mesurer contre un mauvais chemin.

Tests 37 -> 40 : la passe REST enrichit un noeud RENAMED, elle est sautee quand
rien n'est renomme, et son echec ne casse rien. Falsification : 5 mutants, tous
rouges (dont M5 : passe REST supprimee).

Closes #19251

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* docs(ci,#19251): le docstring de tete annonce trois sources, pas deux

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
myia-ai-01 added a commit that referenced this pull request Oct 6, 2026
…esurable (#19303)

* feat(tools,#19297): organe check_series_finish -- finition de série mesurable

Implementation du regime de finition valide par le mainteneur (cf
docs/reference/finition-de-serie.md, PR #19298) :
- README de serie doit avoir une section `## Objectifs d'apprentissage` (H2/H3 strict)
- Chaque carnet du chemin principal doit avoir 3 blocs H2/H3 :
  `## A retenir`, `## Verifiez votre comprehension`, `## Pour aller plus loin`

Criteres mesures sur le corpus reel (1492 carnets au 2026-10-05) :
- A retenir : 14 % des carnets
- Verifiez votre comprehension : 1 %
- Pour aller plus loin : 18 %
- Objectifs README : manquant dans 5 series sur 14 (Complexity, Compression, NLP, RL, SymbolicAI)

API : `--series <nom> --json` (sortie JSON) ou `--report` (sortie texte).
Codes de sortie : 0 (serie finie), 1 (au moins 1 carnet manque 1 bloc, ou README
sans Objectifs), 2 (serie inconnue, nom invalide).

Tests : 24 verts (TestCheckNotebookBlocks, TestCheckReadme, TestRunCheck, TestMainCLI)
avec carnets synthetiques (lecon #19215 : 17 tests verts, plantage sur le vrai
corpus -- ici on isole en tmp_path via monkey-patch SERIES_ROOT).

Rejeu sur le corpus reel : 0/9 series testees finies (ML 0/23, Search 0/73,
RL 0/22, SymbolicAI 0/171, Complexity 0/6, Compression 0/1, NLP 0/5+,
Probas 0/28, GameTheory 0/94). C'est la mesure de depart ; les series
passees en finition recevront leurs blocs en priorite 1 (Search carte validee
#19253, RL #19255, puis les 4 series sans Objectifs).

Grain: MED/tooling -- lane myia-ai-01:CoursIA-2 -- prev: LIGHT/infra #19057

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(tools,#19303): 3 defauts revue coord -- capstone \b, mesure recursive, canonique vs variantes

Issue #19303, revue coord 05/10 (c.5994494493) :

1. **Capstone regex `\b`** : le pattern `n/?a` matchait le 'na' de 'analyse' (et de
   'analyse des besoins', etc.) dans tous les READMEs. Capstone_phrase valait
   'na' sur ML/Search/Probas/GameTheory/RL/SymbolicAI/Complexity/NLP. Fix :
   `n\s*/?\s*a` est devenu `\bn\s*/?\s*a\b` (word boundary explicite des deux
   cotes). Ajout d'un test `test_capstone_pas_de_match_dans_analyse` qui verifie
   qu'un README avec 'analyse approfondie' ne detecte pas de capstone.

2. **Filtre `depth > 1`** : retirait la majorite des carnets (ML 105/128, Search
   69/159, SymbolicAI 132/320, Probas 34/77). La doc finition-de-serie.md dit
   pourtant : "Le chemin principal d'une serie est l'ensemble de ses carnets,
   sous-dossiers compris." Mesure recursive par defaut, exclusion _archive /
   _output / .ipynb_checkpoints uniquement. Nouveau test `test_serie_mesure_recursive`.

3. **Instrument != doc** : separation `canonique` (titres du tableau de la doc)
   et `variante` (titres voisins reconnus, ex. 'Summary', 'Quiz', 'Bibliographie').
   Sortie structuree : pour chaque bloc, deux booleens `canonique[cle]` /
   `variante[cle]`, et `renommer` = variantes sans canal canonique (a renommer),
   `missing` = blocs absents (a ecrire). Compteurs agreges `renommer_total` et
   `ecrire_total` au niveau serie. Permet de repartir le travail (le 14%/1%/18%
   du comptage de depart sont en realite des variantes a renommer, pas des
   blocs absents a ecrire).

Tests : 31 pytest, 0.22s, tous verts (24 + 7 nouveaux = variantes + capstone
\b + tests renommer).

Grain: MED/tooling -- lane myia-ai-01:CoursIA-2 -- prev: MED/tooling #19305

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(tools,#19303): nit-fixes revue 6004712644 -- _norm mort + H2 docstring

La revue 6004712644 releve 2 nits non bloquants apres retrait des 5
commits README de la branche (qui sont transferes dans la PR dediee
feature/19297-readme-finish-objectifs, a venir) :

1. **l.98 _norm mort** : la fonction `_norm` etait definie mais jamais
   appelee (recherche tolerante sur accents). Retire, ainsi que
   l'import `unicodedata` devenu orphelin.
2. **docstring dit H2 mais regex `#{2,3}` accepte H2/H3** : la
   docstring module (l.8) et la docstring `verifie_3_blocs` (l.128)
   disent "H2" ou "3 blocs H2" ; la regex `H2_H3_HEADING_RE` accepte
   2 ou 3 caracteres `#`. Aligne la doc sur le code : "H2/H3".

31/31 tests verts apres les 2 retouches (les tests isoles en tmp_path
ne voient pas _norm et ne comptent pas les H3 explicitement -- la
couverture tient).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(tools,#19478): nit-fixes check_series_finish -- docstring + exclusion suffixe *_output.ipynb

Suivi des residus de la revue f9f2b2f (organe check_series_finish,
PR #19303), declares dans l'issue #19478 :

1. **Docstring l.14-15 obsolete** : "le chemin principal exclut les
   sous-dossiers archive/output, et ne considere que les carnets
   presents au top-level d'une sous-serie (pas les recursifs
   profond)" -- le code est recursif (`root.rglob`, l. 113), et c'est
   ce que la revue avait demande. Reecrit selon le comportement
   reel, en citant finition-de-serie.md.

2. **Exclusion par suffixe de fichier** : `_output` ne portait que
   sur les dossiers (par `split("/")`). Un fichier `X_output.ipynb`
   isole etait comptabilise comme carnet de la serie. Le corpus
   actuel n'en contient aucun hors `_archive` (verifie), donc
   l'effet est nul aujourd'hui -- mais la regle manquait. Corrige
   par ajout d'un test d'exclusion par `p.name.endswith("_output.ipynb")`,
   avec controle non-regression sur les segments `_archive` /
   `_output` / `.ipynb_checkpoints` deja exclus.

3. **Tableau du body de #19303** : "A ecrire" affiche " -- " pour
   5 series alors que l'organe rend des nombres (RL 104,
   SymbolicAI 934, Complexity 30, Compression 3, NLP 15).
   Constat, pas d'action : le premier rapport d'organe post-merge
   qui cite ces series se regle dans le tableau.

Critere de fermeture #19478 : (1) + (2) corriges + tests ; (3)
constate. 5/5 tests PASSED (test_excludes_file_with_output_suffix
+ test_does_not_exclude_carnet_with_output_in_middle_of_name +
test_excludes_output_directory_recursively +
test_excludes_archive_and_checkpoints_segments +
test_recursive_deep_subdirectory_is_included).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: jsboige <jsboige@gmail.com>
Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige
jsboige deleted the fix/19101-credited-sweep branch October 7, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants