Repository navigation
feat(ci,#19101): reveiller la branche credited-examples de exercises-advisory (balayage post-mortem) - #19215
Conversation
…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>
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
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 |
myia-ai-01
left a comment
There was a problem hiding this comment.
🔴 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 :
- Lire
changeTypepar GraphQL (pullRequest.files.nodes { path changeType }), ou classer les carnets avecgit diff --name-status <base>...<merge_commit>. - Passer
head_ref(le commit de merge ouheadRefOid) àcheck_notebooks. - Ajouter un test qui passe par la forme réelle de
gh pr list --json files(sanschangeType) et sur un carnet supprimé. - Ne plus masquer un plantage derrière
|| echo: laisser l'étape échouer. - 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>
|
Reponse point par point a la review — commit 1. 2. 3. Tests sur la forme reelle + carnet supprime. 17 -> 27 :
4. Masque 5. Rejeu 24 h sur le corpus reel (05/10 ~09:05Z, depuis un worktree de la branche, mode rapport seul) : 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, Residu assume (deja declare dans la PR) : RENAMED reste non mesure — 🤖 Generated with Claude Code |
myia-ai-01
left a comment
There was a problem hiding this comment.
🟡 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) :
- compter les exercices sur le blob a
head_refquand il est fourni, comme le fait deja le diff ; - 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 listeerrors, 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>
|
Réponse à la review 5411248369 — commit Voie 1 — le comptage lit la révision de la PR. 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 Tests : 27 → 32. Les nouveaux portent sur un dépôt git réel (deux commits, renommage effectif), pas sur un dict injecté :
Falsification : 3 mutants, 3 rouges — comptage remis sur l'arbre (2 tests), repli par PR retiré (1), Rejeu demandé, Les deux PR du cas qui plantait sont mesurées : Suivi RENAMED — issue #19251, ouverte avant ce merge : 🤖 Generated with Claude Code |
myia-ai-01
left a comment
There was a problem hiding this comment.
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 96contre 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 niFileNotFoundError, 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
Path-collision (organ #13359/#13615)Cette PR #19215 (
Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur |
|
[ADJOINT PREFLIGHT] Dossier tiers (ai-01, lane distincte de l'autrice po-2026:CoursIA). Lu à la tête
|
… after #19215 squash-merge) # Conflicts: # scripts/notebook_tools/credited_examples_sweep.py # scripts/tests/test_credited_examples_sweep.py
…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>
…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>
Grain: MED/guard — lane myia-po-2026:CoursIA — prev: MED/guard #19214
See #19101 — la branche « exemples credites » de
exercises-advisory.ymlest 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 labelcredited-examples-lost-- la branche posee par #18761 etait dormante sousschedule.Preuve:
python -m pytest scripts/tests/test_credited_examples_sweep.py-> 17 passed (3 mutants falsifies) ; mesure 24 h surmain: 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 dansexercises-advisory.yml. Hors scope : l'option 2 (declencheurpull_requestleger), toute re-introduction de clone par PR.Le defaut
La branche
--base+--pr-body-fileposee par #18761 etait dormante. Le diff des exemples credites exige les deux :Or sous
schedule— le seul declencheur qui subsiste apres la tranche 1 de #12817 — il n'y a pas de contexte PR :PR_NUMBERest vide, doncPR_BODY_FILEreste vide, donc la condition est fausse et la brancheelseest prise.BASEetait bien resolu (fenetre nocturne de 24 h) mais ne servait a rien : le diff n'etait jamais mesure, et le labelcredited-examples-lostne 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_requestn'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 eteADDEDpar la PR. Un carnet neuf ne peut rien perdre.Ce n'etait pas un detail cosmetique :
credited_diff_errors > 0bloque 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 100sont le plafond, pas le compte -- cf #19209). 39 touchent un.ipynb:ADDED, rien a perdre par construction)RENAMED, declares NON MESURES)Les deux renommes vivent dans les PRs #19153 et #18986 (ICT-Series) :
gh pr view --json filesn'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) :--limitqui 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) ;merge-baseindisponible est nomme en repli, pas silencieux ;ADDEDn'est pas mesure et est nomme ;RENAMEDest declare non mesure (un renommage peut perdre) et ne bloque plus la mesure des carnets modifies de la meme PR ;Dents verifiees par falsification (3 mutants, 3 echecs nommes, restaure byte-identical) : plafond silencieux,
RENAMEDre-assimile aMODIFIED,would_labelignorant 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).
check_notebookscomptait les exercices sur l'arbre du jour alors que le diff credite lisait deja le blob dehead_ref. Un carnetMODIFIEDpar une PR puis renomme par une suivante (feat(notebook-python,#18086): ICT-45 volet J-lens étendu au Qwen3.5-27B (4e échelle, lens publié + organe --offload) #18788 puis rename(#16231): ICT-45 -> ICT-42b -- accrétion de la famille InoculationBifurcation (chantier C) #19153 pour ICT-45 -> ICT-42b) est absent de l'arbre : le comptage levait unFileNotFoundErrorqui emportait TOUT le balayage (mesure--hours 72, rc=1). La classification reste sur le chemin d'origine :classify_notebooklit des regles de repertoire, que le fichier temporaire du blob ne porte pas. Effet de bord voulu et epingle : quand le carnet existe dans l'arbre, c'est malgre tout la revision de la PR qui est comptee.checkest nomme (« carnet illisible depuis l'arbre du jour (FileNotFoundError: ...) -- renomme ou supprime apres merge, PR ecartee ») et la fenetre continue. Portee etroite (OSError,ValueError) : unRuntimeErrorremonte — un mutant elargi le prouve.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_refl'arbre sert encore ; un echec est nomme et les autres PR sont mesurees ; unRuntimeErrorremonte. Falsification : 3 mutants, 3 rouges.Rejeu
--hours 72(mode rapport, aucun--apply), rc=0 :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_requestleger, 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 quecheck_credited_examplesne sait pas faire aujourd'hui (--baseest une ref git). Non traitee ici.🤖 Generated with Claude Code