Skip to content

fix(notebook-tools,#18146): count_exercises sees scaffolded stubs, bold titles, and correct pairing - #18181

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/18146-count-exercises-stubs
Sep 29, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/18146-count-exercises-stubs

Conversation

@jsboige

@jsboige jsboige commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Grain: DEEP/tooling -- lane myia-po-2026:CoursIA -- prev: MED/notebook #18180

Closes #18146

Constat repris

count_exercises.py rendait 0 exercice sur des notebooks qui en portent trois (54 notebooks relevés par la flotte, 4 reproduits par ai-01 sur main). Deux mécanismes, corrigés ici.

Mécanisme 1 — la garde « le corps calcule » avalait des stubs échafaudés

Quatre causes distinctes, chacune mesurée sur un notebook cité :

  1. Commentaire de fin de ligne lu comme opérateur binaire : return 0.0; // TODO etudiant (GT-13 c22/24/26) et return null; // TODO etudiant : remplacer... (Z3-01 c15/16/17) — les slashes du commentaire satisfaisaient le regex d'expression binaire [+\-*/%] de _return_is_derived, marquant le retour comme calculé. Fix : le commentaire de queue est strippé avant l'analyse de l'opérande ; C# null reconnu comme None.
  2. Docstring comptée comme lignes de code effectives : GT-07 c29 (10 lignes de docstring + # TODO etudiant + un constructeur + return game) — la prose seule franchissait le seuil de 3 lignes. Fix : _strip_docstrings avant comptage.
  3. Retour de gabarit lu comme résultat : return { ouvrant un dict multi-lignes dont toutes les valeurs sont des placeholders (GT-07 c27 : {'spe_offer': 0, ..., 'j2_accepts': []} ; c31 : {'is_spe': False, ..., 'violations': ['TODO etudiant : ...']}). Fix : _return_literal_is_template — un littéral dont TOUTES les valeurs matchent PLACEHOLDER_VALUE_RE (None/null/0/0.0/False/[]/{}/()/""/string-TODO) est un gabarit, pas un calcul. True n'est PAS placeholder (un flag de solution est une sortie ordinaire).
  4. Corps d'une seule ligne de logique : game = ExtensiveFormGame(...) + return game (GT-07 c29) — l'affectation-échafaudage lisait le retour de nom nu comme dérivé. Fix : _body_computes_result exige >= 2 lignes de LOGIQUE (signatures/returns/accolades exclues, _NON_LOGIC_LINE_RE). Le contrôle négatif [D01] Le compteur d'exercices s'inverse : 3 exercices resolus comptes 3 ouverts, 3 sujets ouverts comptes 0 -- et le catalogue n'a aucun champ de role pedagogique #15080 (R05 c30 : ~10 lignes de pipeline de mesure) reste une solution.

Mécanisme 2 — stubs vus mais non appariés

  1. Titres en gras non-ATX : PT_13 c41/43/45 ouvrent par **Exercice 1 — Réintroduire /std dans Dr. GRPO.** Reprendre... — jamais lus par MARKDOWN_HEADER_RE (ATX seul). Fix : BOLD_TITLE_RE reconnaît un span gras en tête de ligne dont le texte COMMENCE par le mot exercice — une ouverture de prose comme **Pourquoi cet exercice est fondamental** : (NumPy-1.2 c27) n'est pas un titre (sans cette garde : corpus 6->12 sur ce notebook). Dédup par numéro dans la cellule (une reprise ATX+gras du même Exercice N = 1 instance).
  2. Énoncé APRÈS le stub (PT_13 : stub i, énoncé gras i+1) : l'appariement arrière n'acceptait que les numéros égaux ; les stubs PT_13 ne portent ni numéro ni mot « exercice ». Fix : appariement par préférences ordonnées — (1) correspondance par numéro (avant puis arrière), (2) stub adjacent (idx-1) sans numéro NI auto-description (son identité EST l'énoncé qui le suit), (3) avant générique. Garde anti-double-appariement (paired_code_indices) sur toutes les branches numérotées.
  3. Titres « Attendus/Exemple guidé : correction de l'exercice N » volant le stub suivant (rl_1c 8->4, Search-03b/c 6->3, SW-15 8->4, ICT-Greffe5, QC-Py-07...) : le scan avant s'arrête sur une cellule titre intermédiaire — le titre le plus PROCHE possède le territoire jusqu'à son stub. Les titres numéros bloqués diffèrent en « unpaired » seulement si leur chaîne de blocage se termine sans write-space (résolution différée post-boucle, préserve le test CSK [D01] Le compteur d'exercices s'inverse : 3 exercices resolus comptes 3 ouverts, 3 sujets ouverts comptes 0 -- et le catalogue n'a aucun champ de role pedagogique #15080 D01).
  4. TOC pluriel re-statement : une cellule section ## 10. Exercices dont les sous-mentions gras restatent les énoncés individuels (PT_10 3->6) ne repousse pas via la voie conservatrice sans-numéro (_first_title_is_plural). La voie conservatrice SURVIT pour un titre singulier sans numéro bloqué (Video 03-2 : stub à 7 cellules de distance — 3 restaurés).

Critères d'acceptation

1. Les 4 notebooks comptent 3 — vérifiés par reproduction directe (count_exercises_in_notebook) :

  • GameTheory-07-ExtensiveForm.ipynb : 0 -> 3
  • GameTheory-13-ImperfectInfo-CFR-Csharp.ipynb : 0 -> 3
  • Z3-01-Introduction-CSharp.ipynb : 0 -> 3
  • PT_13_dapo_drgrpo_corrections.ipynb : 0 -> 3
    Fixtures minimales par forme dans TestScaffoldedStubShapes + TestBoldTitlesAndPairing (11 nouveaux tests).

2. Contrôle négatif #15080 conservé : TestD01CompletedSolutionWithTodo passe inchangé (R05 c30/c32/c34 : solution complète + # TODO etudiant résiduel = exemple, pas exercice). 112 tests pré-existants verts.

3. Mesure avant/après corpus entier (--json, 1259 notebooks) :

  • 86 notebooks changent de compte ; 26 bascules de verdict conforming, TOUTES False->True, 0 True->False.
  • Les 26 bascules recoupent la liste flotte de l'issue : GT-04/07/08/12/13/15c/16, SocialChoice-04, PT_13, OWUI-02/04, SK-02-Advanced, Claude-CLI-04, 3.8/3.9b, App-16/19, Search-02b/03, MGS-13, Planners-8/9, SC-08, Z3-01, Z3-16c (0->4), Tweety-08.
  • Échantillon relu à la main dans les deux sens :
    • Baisses (sur-comptes corrigés) : rl_1c 8->4 (les 4 « Exemple guidé : correction de l'exercice N » volaient le stub suivant) ; GT-02 7->3 (cellule section groupée + titres individuels revendiquaient les mêmes stubs) ; Search-03b 6->3 (les 3 « Attendus — Exercice N » volaient) ; SW-15 8->4 ; QC-Py-07 6->3.
    • Hausses : GT-17b 6->9 (les énoncés groupés **Exercice 4.**/**Exercice 5.** des cellules ### Exercices 4 et 5 comptent désormais) ; Lean-29 reste 6 (les stubs -- Exercice N s'apparient à LEUR titre, plus de double-compte) ; les 0->3 de la liste flotte.
  • Résiduel documenté (hors scope, pas d'élargissement aveugle) : GT-02 Exercice 4 (return A.copy(), B.copy(), [] # TODO etudiant — tuple d'attributs encore lu comme dérivé) reste manqué ; l'erreur du notebook passe de +4 (7 vs 3+1 solution... en réalité 7 vs 4) à -1 (3 vs 4).

4. Pas d'élargissement aveugle : chaque nouvelle forme est justifiée par un notebook cité dans les docstrings (GT-07 c27/c29/c31, GT-13 c22, Z3-01 c15, PT_13 c40-45, NumPy-1.2 c27, Lean-29 c5-8, rl_1c c45-56, PT_10 c19-24, Video 03-2 c16-24, GT-17b c12-24, Search-03b c26-34).

Tests

  • test_count_exercises.py : 123 passed (112 pré-existants + 11 nouveaux).
  • Suite scripts/ complète : voir commentaire ci-dessous (post-push).

🤖 Generated with Claude Code

…gate swallowed + bold titles + pairing preferences

Four gate fixes (_body_computes_result / _return_is_derived):
- strip tail comments before operand analysis (C# // TODO slashes read as division)
- exclude docstring lines from effective-code-line count
- treat all-placeholder return dicts/lists as templates, not computed results
- require >=2 logic lines: single scaffolding assignment no longer makes a naked return derived

Pairing fixes:
- recognize **Exercice N** bold titles (span starts with the exercise word; number-dedup in cell)
- preference order: same-number backward/forward, then adjacent numberless undescribing stub, then forward generic; paired_code_indices guard on all numbered branches
- forward scan stops at intervening header cells (nearest header owns its stub)
- deferred unpaired resolution via blocker chains (preserves #15080 negative control)

Corpus measurement (1259 notebooks): 86 count changes, 26 conforming flips all False->True,
0 True->False; the 4 issue-reproduced notebooks count 3 each. 123 file tests (112 + 11 new),
311 consumer tests green.

Closes #18146

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

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).

@clusterManager-Myia clusterManager-Myia 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.

VERDICT: LGTM — review d'exécution, chaque critère d'acceptance de #18146 rejoué sur le code du head.

Ce qui a été réellement exécuté (pas lu — rejoué) :

  1. Suite de tests du head : 123 passed en local (pytest test_count_exercises.py sur les fichiers du head 770e53e) — conforme au claim 112+11, incl. le contrôle négatif #15080 (TestD01CompletedSolutionWithTodo inchangé).
  2. Critère 1 reproduit firsthand, avant/après : les 4 notebooks flotte comptés par l'organe main → head : GameTheory-07-ExtensiveForm 0→3, GameTheory-13-ImperfectInfo-CFR-Csharp 0→3, Z3-01-Introduction-CSharp 0→3 (voie code_cell_comment), PT_13_dapo_drgrpo_corrections 0→3 (voie markdown_header sur titres gras).
  3. Critère 3, échantillon relu dans les deux sens : GT-02-NormalForm 7→3, rl_1c_prolog_distillation 8→4 (baisses = sur-comptes corrigés), GT-17b-Asymmetric-Information 6→9 (hausse, énoncés groupés comptés). Tous conformes aux chiffres du body.
  4. Garde anti-élargissement vérifiée sur son témoin négatif cité : NumPy-1.2 reste 6→6 — la garde « span gras = titre seulement si le texte commence par le mot exercice » fait bien son travail (sans elle, 6→12 annoncé).
  5. Pas de hardcoding : les noms de notebooks (GT-07 c27, PT_13 c41…) n'apparaissent qu'en docstrings de justification (critère 4) — aucun dans la logique de comptage.

Résiduel honnête : GT-02 Exercice 4 (tuple d'attributs lu comme dérivé) reste manqué et l'erreur passe de +4 à −1 — documenté dans le body, hors scope légitime.

PR gate rouge = DWELL uniquement (tête 70 min < plancher 120 min, 19/19 checks substantiels verts, y compris Scripts Tests qui couvre directory-wide) — jambe minuteur, rien à corriger dans le code, se ré-agrège au balayage.

Le résidu cell-interpretation-ordering de PT_13 (énoncé après stub) est bien séparé de cette PR — l'appariement par préférences ordonnées est le bon périmètre ici.

[Hermes hermes-pr-review, cycle :05 28/09, host f6be46d1b7a3, sig=57ace41a]

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #18181 (fix(notebook-tools,#18146): count_exercises sees scaffolded stubs, bold titles, and correct pairing) 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.

@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2023:CoursIA
pr: 18181
head: 770e53e
complete: true
body: read
comments-reviewed: 2
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: dba0f913af6f37bf3c207a76fec1dfb93bf7272c557d8b0ea678a468185f070c
diff-files: 2
diff-additions: 600
diff-deletions: 67
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
[/ADJOINT PREFLIGHT]

Note de lecture (informatif) : sticky PR-PATH-COLLISION present (ordre de merge a croiser avec les PRs citees).

@myia-ai-01
myia-ai-01 merged commit 38985b9 into main Sep 29, 2026
21 of 24 checks passed
myia-ai-01 pushed a commit that referenced this pull request Sep 29, 2026
…s comment-only cross-ref (#18182)

classify partitioned open PRs into one undifferentiated reason; a census
comment on an unrelated open PR (permanent cross-referenced event, e.g.
#17434 freezing #15173/#15703) read as "work in progress" where it was a
contextual mention. The reason now names both origins separately:
body/title mention = "declare this issue (plausible next phase)";
otherwise = "comments only (cross-referenced contextual mention, undeclared)".

Verdict unchanged in both cases (reason change first per acceptance 1);
unreadable body defaults to undeclared (fail-safe, #15060 philosophy).
Driver enriches open PR bodies+titles too (_with_merged_pr_bodies ->
_with_pr_bodies; closed-unmerged PRs stay unfetched). Title stays CLOSED
for every labelling decision (#17759 arbitration); delivered not widened.

Firsthand via the real chain: #18146 <- open #18181 renders in_flight
"declare this issue in body/title (plausible next phase)"; the issue's
named instances #15173/#15703 no longer reproduce as in_flight because
#17434 merged since (measured 2026-09-28). 50 tests (44 + 6 new).

Closes #18102

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@jsboige
jsboige deleted the fix/18146-count-exercises-stubs branch October 16, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane-claim-absent Closing issue carries no claim at all (#10223)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(count_exercises): des stubs échafaudés comptés comme solutions — 0/3 sur des notebooks qui en ont trois

3 participants