SLOPSHOPPER

choix-modele

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.

newtoastpromptmodel
A shopper browsing a rack in a slop shop
README

Bac 2027 — révisions de première et de terminale

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éeEspaceRouteÉtat
TerminaleMaths — spécialité/terminale/mathspages créées, contenu à venir
TerminalePhysique-chimie/terminale/physique-chimiepages créées, contenu à venir
TerminaleGrand oral/terminale/grand-oralpages créées, contenu à venir
PremièreMaths — spécialité (EAM)/premiere/mathscomplet
PremièreFrançais (EAF écrit + oral)/premiere/francaiscomplet
—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).

Modules

Maths — première (/premiere/maths)

  • 📋 Formulaire — cartes de référence par chapitre
  • ⚡ Automatismes — QCM rapides au format Partie 1 de l'EAM
  • 📝 Exercices classiques — applications directes avec correction progressive
  • 🎓 Exercices type bac — sujets multi-questions au format Partie 2
  • 🧪 Bac blanc — sujets complets chronométrés (3 sujets EAM 2026)

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

Français — Écrit (/premiere/francais)

  • 📖 Fiches — fiches de méthode et de connaissances par module
  • ⚡ Quiz — QCM, questions à choix multiples et exercices de remise en ordre
  • ✍️ Exercices — exercices de repérage guidé avec correction progressive

Modules disponibles : methode-commentaire, methode-dissertation, figures-de-style, mouvements-litteraires, registres-genres, poesie, litterature-idees, roman, theatre, express (révision rapide).

Français — Oral (/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.

Stack

Vite · React 18 · TypeScript · Tailwind · KaTeX · Zustand · React Router

Déployé sur GitHub Pages via GitHub Actions.

Démarrage

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

Structure

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

Contenu pédagogique

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

Workflow d'ajout de contenu

Maths :

  1. /new-chapter <slug> — scaffolding
  2. Sub-agent chapter-author — génération
  3. Sub-agent pedagogical-reviewer — validation 2-passes
  4. /verify-conformity — validation globale avant commit

Français (sous content/francais/, validation node scripts/validate-francais.mjs) :

  1. /new-module-francais <slug> — scaffolding
  2. Sub-agent french-content-author — génération
  3. Sub-agent french-reviewer — validation 2-passes (dont passes factuelle et citations bloquantes)
  4. /verify-francais — validation globale avant commit

Le contenu français est en JSON typé sous content/francais/<module>/, validé par les schémas de schemas/francais/.

Programme couvert

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 :

  • Algèbre : suites numériques, second degré
  • Analyse : dérivation, fonction exponentielle, trigonométrie
  • Géométrie : produit scalaire, géométrie repérée du plan
  • Probabilités-Statistiques : probabilités conditionnelles, variables aléatoires
  • Algorithmique et programmation (transversal — non couvert en v1)

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.

Contenu disponible dans l'app

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

Déploiement

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/

Licence et usage

Outil personnel à usage privé. Le contenu pédagogique est généré à partir des programmes officiels (textes publics). Pas de redistribution prévue.

Source 1 files
hooks/register.tsx 102 lines
1import 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