Au premier prompt, évalue le modèle et l'effort choisis et propose de les changer s'ils paraissent mal adaptés à la tâche.

Application web statique de révision couvrant les deux années du baccalauréat session 2027. La navigation est unique : une colonne à gauche, groupée par année, et deux outils transverses.
| Année | Espace | Route | État |
|---|---|---|---|
| Terminale | Maths — spécialité | /terminale/maths | pages créées, contenu à venir |
| Terminale | Physique-chimie | /terminale/physique-chimie | pages créées, contenu à venir |
| Terminale | Grand oral | /terminale/grand-oral | pages créées, contenu à venir |
| Première | Maths — spécialité (EAM) | /premiere/maths | complet |
| Première | Français (EAF écrit + oral) | /premiere/francais | complet |
| — | Simulateur de moyenne | /simulateur | à porter |
| — | Le bac, mode d'emploi | /le-bac | à remplir |
Les anciennes adresses (/chapitre/*, /bac-blanc, /francais/*) redirigent automatiquement vers les nouvelles.
Les volets partagent le même code applicatif mais leurs données restent strictement isolées : stores LocalStorage distincts (bms-2026-* pour les maths, bfr-2026-* pour le français).
/premiere/maths)Chapitres disponibles : suites, derivation, second-degre, exponentielle, trigonometrie, produit-scalaire, geometrie-reperee, probas-cond, variables-aleatoires.
Thème clair/sombre disponible (bouton dans l'en-tête).
/premiere/francais)Modules disponibles : methode-commentaire, methode-dissertation, figures-de-style, mouvements-litteraires, registres-genres, poesie, litterature-idees, roman, theatre, express (révision rapide).
/premiere/francais/oral)Espace par élève (/premiere/francais/oral/<eleve>) : présentation de l'épreuve, méthode, fiches de grammaire + quiz, analyses linéaires des textes étudiés, questions d'entretien, œuvre choisie (2ᵈᵉ partie de l'oral), simulateur d'entretien chronométré. Le contenu commun (épreuve, méthode, grammaire) est partagé entre élèves ; les textes et l'entretien sont propres à chaque élève.
Vite · React 18 · TypeScript · Tailwind · KaTeX · Zustand · React Router
Déployé sur GitHub Pages via GitHub Actions.
npm install
npm run dev # serveur de développement
npm run build # build production
npm run preview # prévisualiser le build
npm run test # tests Vitest
.
├── CLAUDE.md # référentiel pour Claude Code
├── .claude/
│ ├── skills/ # skills : programmes officiels (maths + français)
│ ├── agents/ # sub-agents (maths + français)
│ └── commands/ # slash commands (maths + français)
├── schemas/ # JSON Schema maths (validation Ajv)
│ └── francais/ # JSON Schema français
├── content/
│ ├── chapters/<slug>/ # contenu maths (5 fichiers JSON par chapitre)
│ └── francais/<module>/ # contenu français (meta + fiches/quiz/exercices)
└── src/
├── ... # application React maths
└── francais/ # application React français (additive)
Tout le contenu (formules, exercices, sujets) est en JSON typé sous content/chapters/<slug>/, séparé du code applicatif. Validation par schémas (Ajv) et revue pédagogique automatisée (sub-agent).
Maths :
/new-chapter <slug> — scaffoldingchapter-author — générationpedagogical-reviewer — validation 2-passes/verify-conformity — validation globale avant commitFrançais (sous content/francais/, validation node scripts/validate-francais.mjs) :
/new-module-francais <slug> — scaffoldingfrench-content-author — générationfrench-reviewer — validation 2-passes (dont passes factuelle et citations bloquantes)/verify-francais — validation globale avant commitLe contenu français est en JSON typé sous content/francais/<module>/, validé par les schémas de schemas/francais/.
Programme de spécialité mathématiques de Première (BO spécial n°1 du 22 janvier 2019), en vigueur pour l'année scolaire 2025-2026. Cinq parties :
Détail complet : .claude/skills/bac-maths-premiere-spe-2026/SKILL.md
Pour le français : programme de l'EAF (4 objets d'étude — poésie, littérature d'idées, roman, théâtre). Détail complet : .claude/skills/bac-francais-premiere-2026/SKILL.md.
Maths — neuf chapitres du programme de spécialité (algèbre, analyse, géométrie, probabilités-statistiques) ; l'algorithmique et la programmation (transversale) ne sont pas couvertes.
Français — dix modules de l'écrit (méthodologie, connaissances génériques et par objet d'étude) et l'espace oral complet (par élève).
Push sur la branche main → workflow GitHub Actions → déploiement automatique sur GitHub Pages.
URL de déploiement : https://<user>.github.io/bac-maths-1ere-spe-2026/
Outil personnel à usage privé. Le contenu pédagogique est généré à partir des programmes officiels (textes publics). Pas de redistribution prévue.
hooks/register.tsx 102 lines1import type { Register } from 'claude-code'
2
3type Level = 'low' | 'medium' | 'high' | 'xhigh' | 'max'
4
5const LEVELS: readonly Level[] = ['low', 'medium', 'high', 'xhigh', 'max']
6
7const MODELS = {
8 haiku: 'claude-haiku-5-5',
9 sonnet: 'claude-sonnet-5-5',
10 opus: 'claude-opus-5-5',
11 fable: 'claude-fable-5-1',
12} as const
13
14type Alias = keyof typeof MODELS
15
16type Verdict = { model: Alias; effort: Level; reason: string }
17
18const family = (id: string): Alias | undefined =>
19 (Object.keys(MODELS) as Alias[]).find(a => id.toLowerCase().includes(a))
20
21const JUDGE = `Tu évalues si le modèle et l'effort de réflexion choisis par l'utilisateur conviennent à sa première demande dans une session Claude Code.
22Modèles (du moins au plus capable/coûteux) : haiku (tâches triviales : renommer, lire, petite question), sonnet (code et contenu courants, la plupart des tâches), opus (raisonnement difficile, architecture, gros refactor, relecture critique), fable (le plus capable : très long travail autonome, recherche complexe).
23Effort : low (trivial), medium (courant), high (multi-étapes, plusieurs fichiers), xhigh (conception ou débogage difficiles), max (cas extrêmes seulement).
24Règle : recommande le plus petit couple suffisant ; ne propose un changement que s'il est net (un cran d'écart sur une demande ambiguë = garder le choix actuel). Les questions simples et les petites retouches n'ont pas besoin d'opus ni d'un effort élevé.
25Réponds UNIQUEMENT par un JSON : {"model":"haiku|sonnet|opus|fable","effort":"low|medium|high|xhigh|max","reason":"une phrase courte en français"}.`
26
27const parse = (text: string): Verdict | undefined => {
28 const raw = text.match(/\{[\s\S]*\}/)?.[0]
29 if (raw === undefined) return undefined
30 try {
31 const v = JSON.parse(raw) as Partial<Verdict>
32 const model = v.model
33 const effort = v.effort
34 if (model === undefined || !(model in MODELS)) return undefined
35 if (effort === undefined || !LEVELS.includes(effort)) return undefined
36 return { model, effort, reason: String(v.reason ?? '') }
37 } catch {
38 return undefined
39 }
40}
41
42export const register: Register = on => {
43 let isChecked = false
44 // Choix accepté : appliqué à chaque requête de la boucle principale.
45 let override: { model: string; effort: Level } | undefined
46
47 on('prompt.submit', async ($, e, next) => {
48 if (isChecked || e.origin.kind !== 'user') return next(e)
49 isChecked = true
50
51 try {
52 const current = await $.session.model()
53 const rows = await $.config.list()
54 const effortRow = rows.find(r => /effort/i.test(r.key))
55 const modelRow = rows.find(r => r.key === 'model')
56 const effort = typeof effortRow?.value === 'string' ? effortRow.value : 'inconnu'
57
58 const res = await $.model.complete({
59 model: 'haiku',
60 effort: 'low',
61 system: JUDGE,
62 prompt: `Modèle actuel : ${current}\nEffort actuel : ${effort}\nPremière demande :\n${e.text.slice(0, 4000)}`,
63 })
64 if (!res.isAnswered) return next(e)
65 const v = parse(res.text)
66 if (v === undefined) return next(e)
67
68 const sameModel = family(current) === v.model
69 const sameEffort = effort === 'inconnu' || effort === v.effort
70 if (sameModel && sameEffort) return next(e)
71
72 const target = `${v.model}, effort ${v.effort}`
73 const keep = `Garder (${family(current) ?? current}, effort ${effort})`
74 const change = `Passer à ${target}`
75 const answer = await $.ui.ask(
76 `${v.reason} Changer le modèle/l'effort pour cette session ?`,
77 { header: 'Modèle', options: [change, keep] },
78 )
79 if (answer !== change) return next(e)
80
81 override = { model: MODELS[v.model], effort: v.effort }
82 if (modelRow !== undefined && modelRow.options?.includes(v.model)) {
83 await $.config.set({ key: modelRow.key, value: v.model })
84 }
85 if (effortRow !== undefined && effortRow.options?.includes(v.effort)) {
86 await $.config.set({ key: effortRow.key, value: v.effort })
87 }
88 $.ui.toast(`Session : ${target}`)
89 } catch {
90 // question refusée, fermée ou impossible (mode -p) : on ne bloque jamais le prompt
91 }
92
93 return next(e)
94 }).catch(($, e, next) => (next.called ? next(e) : next(e)))
95
96 on('turn.step', async function* ($, e, next) {
97 const isMain = override !== undefined && e.agentId === undefined
98
99 return yield* next(isMain ? { ...e, model: override!.model, effort: override!.effort } : e)
100 })
101}
102