Ce module pose le vocabulaire, la mécanique d'un modèle de langage et ses limites. Les démonstrations utilisent le mode non interactif de Claude Code : claude -p "question" répond puis rend la main, ce qui permet de montrer une réponse brute sans ouvrir de session.
Le vibe coding consiste à produire un logiciel en décrivant ce que l'on veut, en français ou en anglais, à un modèle de langage qui écrit le code. Au lieu de taper :
@app.get("/api/questions")
def list_questions():
with open("data/questions.json") as f:
return json.load(f)
on écrit :
Ajoute une route GET /api/questions qui renvoie le contenu de data/questions.json,
sans les bonnes réponses.
L'unité de travail passe de la ligne de code à l'intention, et le message envoyé au modèle s'appelle une invite (prompt). Au sens strict, le vibe coding désigne une pratique radicale : on accepte le code sans le lire, on juge au résultat, on relance quand ça ne marche pas. Dans l'usage professionnel, le terme s'est élargi à tout développement où l'IA écrit la majorité du code et où l'humain spécifie, relit et valide. C'est cette acception qui est retenue ici.
Le terme est né le 2 février 2025, dans une publication d'Andrej Karpathy (cofondateur d'OpenAI, ancien directeur de l'IA chez Tesla) sur X :
There's a new kind of coding I call "vibe coding", where you fully give in to the vibes, embrace exponentials, and forget that the code even exists.
« Il y a une nouvelle forme de programmation que j'appelle vibe coding, où l'on s'abandonne complètement aux sensations, où l'on embrasse les exponentielles, et où l'on oublie jusqu'à l'existence du code. » Karpathy acceptait toutes les modifications proposées et collait les messages d'erreur sans les lire. Il parlait de projets du week-end, pas de systèmes en production. L'expression a ensuite servi d'étiquette à tout le développement assisté par IA, d'où beaucoup de malentendus.
Le développement assisté par IA est un continuum. Plus on avance, plus la machine est autonome, et plus le rôle de l'humain se déplace de l'écriture vers la spécification et le contrôle.
┌──────────────┬──────────────┬──────────────┬──────────────┐
│ Autocomplé- │ Chat │ IDE avec │ Agent │
│ tion │ │ IA │ │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ propose la │ répond à une │ modifie les │ lit, écrit, │
│ suite de la │ question, │ fichiers │ exécute, │
│ ligne │ vous copiez │ ouverts │ corrige seul │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ Copilot │ ChatGPT │ Cursor │ Claude Code │
│ │ Claude │ Windsurf │ Codex CLI │
└──────────────┴──────────────┴──────────────┴──────────────┘
autonomie de la machine ──────────────────────────▶
| Niveau | Où il agit | Exemples d'outils |
|---|---|---|
| Autocomplétion | Dans le fichier ouvert, à la position du curseur | GitHub Copilot |
| Chat | Dans un navigateur ou un panneau, hors du dépôt | ChatGPT, Claude, Gemini, Le Chat |
| Agent dans l'IDE | Sur les fichiers du projet, avec un diff à accepter | Cursor, Windsurf, Antigravity |
| Agent en terminal | Sur le dépôt entier, avec exécution de commandes | Claude Code, Codex CLI, Gemini CLI |
La dernière ligne est celle des TP, joués sur le dépôt QuizOps. Voici ce qu'elle donne, sans qu'on lui indique où chercher :
⏺ Bash(grep -n "def \|score" ~/projets/quizops/app/quiz.py)
⎿ 1:"""Tirage des questions et calcul du score.""" (+4 lignes)
⏺ Read(~/projets/quizops/app/quiz.py)
⎿ 15 def sans_reponse(question: dict) -> dict: (+22 lignes)
⏺ Le score est calculé dans `app/quiz.py`, fonction `calculer_score` (avec l'aide de
`compter_bonnes_reponses`). Elle ne garde que les réponses effectivement fournies (celles où
`choix` n'est pas `None`), puis compte parmi elles combien correspondent à la bonne réponse de
la question associée. Le résultat est le pourcentage de bonnes réponses, arrondi à l'entier le
plus proche (`round(bonnes / len(repondues) * 100)`).
[fin] modèle(s) : claude-sonnet-5 ; entrée 6 + cache 75646 ; sortie 419 ; durée 6361 ms
Les mêmes mécanismes existent chez les concurrents, sous d'autres noms. Ces noms changent souvent, parfois d'une version à la suivante ; seul le principe est stable.
| Principe | Claude Code | Cursor | Windsurf |
|---|---|---|---|
| Fichier de contexte du projet | CLAUDE.md |
AGENTS.md |
AGENTS.md |
| Règles ciblées par chemin | .claude/rules/*.md |
.cursor/rules/*.mdc |
Rules |
| Commandes réutilisables | .claude/commands/*.md |
.cursor/commands/*.md |
Workflows |
| Serveurs MCP | .mcp.json |
mcp.json |
mcp_config.json |
| Mode plan | Mode plan (Maj+Tab) |
Plan mode | Plan mode |
Chaque ligne est démontrée sur Claude Code plus loin : le fichier de contexte au module Prise en main, les commandes au module Skills, les serveurs MCP au module MCP et plugins.
Un grand modèle de langage (LLM, Large Language Model) fait une seule chose : à partir d'un texte, il prédit ce qui vient ensuite, un fragment à la fois.
Texte reçu : "Pour lister les conteneurs Docker en cours, on tape docker"
Suites évaluées : " ps" (élevée) " images" (plus faible) " compose" (plus faible)
Le modèle choisit un fragment, l'ajoute au texte, recommence. Il n'y a ni compilation ni vérification : un modèle ne « sait » pas qu'une fonction existe, il estime qu'écrire son nom à cet endroit est vraisemblable.
Ces fragments s'appellent des tokens et valent trois à quatre caractères en moyenne. Tout se compte et se facture en tokens, ce que vous envoyez comme ce que le modèle répond ; le français en consomme plus que l'anglais pour un texte équivalent.
$ claude -p "Combien font 7 fois 8 ?" --model haiku --output-format json \
| jq '{result, entree: .usage.input_tokens, cache: .usage.cache_read_input_tokens, sortie: .usage.output_tokens}'
{
"result": "7 fois 8 = 56",
"entree": 10,
"cache": 17583,
"sortie": 76
}
La question ne pèse que 10 tokens, mais 17 583 tokens ont été lus en cache : ce sont le prompt système de Claude Code et la description de ses outils, envoyés à chaque appel et mis en cache pour ne pas être refacturés au plein tarif. Une question de trois mots coûte donc bien plus que trois mots.
La température règle le degré d'aléatoire dans le choix du token suivant. Les outils de développement fonctionnent à température basse, ce qui limite la variance sans la supprimer. Le même prompt, deux fois, avec le même modèle et dans le même dépôt :
$ claude -p "Écris une fonction Python melanger(liste) qui renvoie une copie mélangée de la liste. Réponds par le code seul." --model sonnet
# première exécution
import random
def melanger(liste):
"""Renvoie une copie mélangée de la liste (algorithme de Fisher-Yates)."""
resultat = liste.copy()
for i in range(len(resultat) - 1, 0, -1):
j = random.randint(0, i)
resultat[i], resultat[j] = resultat[j], resultat[i]
return resultat
# deuxième exécution
import random
def melanger(liste):
resultat = list(liste)
for i in range(len(resultat) - 1, 0, -1):
j = random.randint(0, i)
resultat[i], resultat[j] = resultat[j], resultat[i]
return resultat
L'algorithme et le comportement sont identiques. Ce qui bouge, c'est la docstring et le choix entre liste.copy() et list(liste). Passé deux fois à Haiku, ce même prompt a rendu deux fois exactement le même code, caractère pour caractère.
La variance existe donc, mais elle porte sur les détails quand la tâche est bien définie. Elle porte sur le fond quand elle ne l'est pas, et c'est le sujet du module Prompt engineering et conception. Sur une question dont la réponse est dans un fichier, seule la formulation change :
⏺ Bash(python3 -c " import json d = json.load(open('data/questions.json')) from collec…)
⎿ This command requires approval
⏺ Read(~/projets/quizops/data/questions.json)
⎿ 1 [ (+86 lignes)
⏺ 6 questions "docker" et 6 questions "kubernetes".
⏺ Bash(python3 -c " import json d = json.load(open('data/questions.json')) from collec…)
⎿ This command requires approval
⏺ Read(~/projets/quizops/data/questions.json)
⎿ 1 [ (+86 lignes)
⏺ docker : 6, kubernetes : 6.
La commande Python a été refusée faute de permission dans les deux cas, l'agent est passé par une lecture de fichier, et le compte est le même. Une question qui n'a qu'une seule réponse plausible ne varie pas du tout : quatre exécutions, deux avec Sonnet et deux avec Haiku, ont rendu le même nom.
$ claude -p "Propose un nom de fonction Python pour calculer le score d'un quiz. Réponds par le nom seul." --model sonnet
calculer_score
Un modèle est entraîné jusqu'à une date, puis figé. Tout ce qui est publié après cette coupure de connaissances (knowledge cutoff) lui est inconnu, sauf si vous le lui fournissez.
$ claude -p "Quelle est la dernière version de FastAPI ? Réponds en deux lignes." --model sonnet
Je n'ai pas pu vérifier en ligne (permission refusée), donc voici ma réponse basée sur mes
connaissances, qui datent de janvier 2026 et peuvent être dépassées.
À ma connaissance (janvier 2026), FastAPI était autour de la version 0.115.x. Vérifie sur PyPI
(`pip index versions fastapi`) ou https://pypi.org/project/fastapi/ pour le numéro exact actuel.
$ curl -s https://pypi.org/pypi/fastapi/json | jq -r .info.version
0.141.1
Vingt-six versions mineures d'écart. La réponse est instructive dans les deux issues : le modèle qui date sa connaissance et renvoie vers la source fait exactement ce qu'on attend de lui, et c'est ce comportement qu'il faut savoir reconnaître. Pour lui donner la documentation à jour, on branche un serveur MCP, au module MCP et plugins.
La fenêtre de contexte est la quantité de texte que le modèle a sous les yeux au même instant : votre message, l'historique de la conversation, les fichiers lus, les sorties de commandes, ses propres réponses. La commande /context affiche son occupation, ici sur QuizOps au premier TP, session vierge :
## Context Usage
**Model:** claude-sonnet-5
**Tokens:** 24.1k / 1m (2%)
### Estimated usage by category
| Category | Tokens | Percentage |
|---------------------------|--------|------------|
| System prompt | 8.8k | 0.9% |
| System tools | 13.2k | 1.3% |
| System tools (deferred) | 14.7k | 1.5% |
| Skills | 2.1k | 0.2% |
| Messages | 8 | 0.0% |
| Free space | 943k | 94.3% |
| Autocompact buffer | 33k | 3.3% |
Le prompt système (8,8 k) contient les consignes de l'outil lui-même. Les deux lignes d'outils (27,9 k au total) décrivent ce que l'agent sait faire, la seconde regroupant les outils chargés à la demande. Les skills livrés occupent 2,1 k et la conversation 8 tokens. Le reste est libre, moins un tampon de 33 k que Claude Code réserve pour compacter la session avant la saturation.
Cette fenêtre est volatile : rien ne persiste d'une conversation à l'autre, et un modèle qui a passé une heure sur votre projet repart de zéro à la session suivante. D'où le fichier CLAUDE.md, vu au module Prise en main de Claude Code. Elle est aussi finie, et la qualité se dégrade avant la limite : contrainte oubliée, bug corrigé qui revient, proposition de créer un fichier qui existe déjà.
Une hallucination est une affirmation produite avec assurance et qui est fausse. Le modèle a simplement généré ce qui était le plus vraisemblable.
| Type | Exemple | Comment le détecter |
|---|---|---|
| Fonction inexistante | datetime.parse_iso() en Python |
Le code plante à l'exécution |
| Paramètre inventé | Une option --strict sur une commande qui ne la connaît pas |
La commande renvoie une erreur |
| Dépendance fantôme | pip install fastapi-auth-utils, paquet qui n'existe pas |
L'installation échoue |
| Source imaginaire | Un lien de documentation qui renvoie une erreur 404 | Le lien ne s'ouvre pas |
| Comportement supposé | « Cette fonction trie la liste en place » alors qu'elle renvoie une copie | Rien ne plante, le résultat est faux |
Les quatre premiers types se détectent seuls : ça casse. Le cinquième produit un logiciel qui tourne et qui se trompe. Pour la dépendance fantôme, sachez que des attaquants publient de vrais paquets portant les noms que les modèles inventent le plus souvent.
La demande ci-dessous contient un présupposé faux : l'option --strict-mode d'uvicorn n'existe pas. Le mode d'écriture est pourtant autorisé.
$ claude -p "Documente dans README.md l'option --strict-mode d'uvicorn que le projet utilise au lancement." \
--model sonnet --permission-mode acceptEdits
⏺ Bash(grep -rn "strict-mode\|strict_mode" ~/projets/quizops --include="*.py" --include="*.md"
--include="*.toml" --include="*.json" 2>/dev/null; echo "---"; find ~/projets/quizops -iname "README*")
⎿ --- (+2 lignes)
⏺ I checked the codebase and uvicorn's actual CLI: there's no `--strict-mode` option anywhere.
[…]
[fin] modèle(s) : claude-sonnet-5 ; entrée 4 + cache 52289 ; sortie 722 ; durée 11646 ms
La suite de la réponse, en anglais comme ici, liste les options réelles d'uvicorn, conclut qu'il n'y a rien à documenter et demande si une autre option était visée. L'agent a cherché dans le dépôt avant de répondre, puis a refusé d'écrire. Un chat sans outils, à qui on affirme la même chose, invente plus volontiers une section de documentation. La contre-mesure ne change pas pour autant : c'est vous qui vérifiez le résultat.
Le code s'exécute, se lit bien, porte des noms sensés, et il est faux. QuizOps embarque le cas au tag tp4-depart, que l'on lit avec git -C ~/projets/quizops show tp4-depart:app/quiz.py :
def compter_bonnes_reponses(
reponses: list[dict], questions_par_id: dict[int, dict]
) -> int:
"""Compte les réponses dont le choix correspond à la bonne réponse."""
bonnes = 0
for reponse in reponses:
question = questions_par_id.get(reponse["question_id"])
if question is not None and reponse.get("choix") == question["reponse"]:
bonnes += 1
return bonnes
def calculer_score(reponses: list[dict], questions_par_id: dict[int, dict]) -> int:
"""Score de la partie, en pourcentage de bonnes réponses."""
repondues = [reponse for reponse in reponses if reponse.get("choix") is not None]
bonnes = compter_bonnes_reponses(repondues, questions_par_id)
return round(bonnes / len(repondues) * 100)
Le dénominateur est len(repondues), le nombre de questions auxquelles le joueur a répondu, et non le nombre de questions posées. Un joueur qui répond à 5 questions sur les 10 d'une partie et qui a juste sur ces 5 obtient 100. S'il ne répond à rien, len(repondues) vaut 0 et c'est une division par zéro.
Ce bug traverse la formation : il sert de matière au module Prompt engineering pour montrer ce qu'un prompt précis fait remonter, au module Validation et tests parce qu'un test le tranche sans discussion, et il est corrigé au TP 4.
En programmation classique, vous décrivez l'implémentation ; en vibe coding, vous décrivez l'intention.
| Programmation classique | Vibe coding | |
|---|---|---|
| Ce que vous produisez | Du code | Une spécification en langage naturel |
| Unité de travail | La fonction, la ligne | La fonctionnalité, l'intention |
| Source de vérité | Le code écrit | Le dialogue puis le code relu |
| Boucle de correction | Vous lisez l'erreur et corrigez | Vous transmettez l'erreur et validez la correction |
| Compétence critique | Connaître le langage et ses bibliothèques | Savoir formuler, découper et relire |
| Coût d'un aller-retour | Élevé (écrire, compiler, tester) | Faible (quelques secondes) |
| Risque principal | L'erreur que vous avez écrite | L'erreur que vous n'avez pas vue |
/classement qui affiche les 10 meilleurs scores, pseudo et pourcentage, triés décroissant ».git diff --stat avant de valider, pour voir combien de fichiers ont bougé et si le modèle a débordé.uv run pytest -q doit afficher son compte de tests verts avant que le lot soit considéré comme fini.| Terrain favorable | Terrain à éviter |
|---|---|
| Prototype, maquette, preuve de concept : le code est jetable | Paiement, santé, transport, industriel : une erreur non vue a des conséquences réelles |
| Outillage interne : script de migration, tableau de bord, générateur de rapport | Sécurité et cryptographie : le code a l'air correct et il est vulnérable |
| Code répétitif : tests, sérialisation, appels d'API, migrations | Code ancien peu testé, aux dépendances implicites et sans historique connu |
| Technologie non maîtrisée : voir du code fonctionnel se construire | Optimisation fine : mémoire, temps réel, calcul intensif |
| Documentation et tests d'un code existant | Ce que vous ne savez pas relire : vous ne pilotez plus rien |
Avant de lancer une génération, une question : que se passe-t-il si ce code contient une erreur que je ne vois pas ? Si la réponse est « je relance le prototype », allez-y. Si la réponse est « on perd des données clients », l'IA reste utile mais sous surveillance renforcée, par petits incréments et avec des tests systématiques.
Attention : tout ce que vous envoyez à un service hébergé sort de votre système d'information. Jamais de secret dans un prompt, ni mot de passe, ni clé d'API, ni donnée personnelle réelle. Le sujet revient au module Subagents, orchestration et gouvernance.
Une question de fait, dont la réponse est dans le dépôt :
$ claude -p "Dans QuizOps, quel fichier contient le calcul du score et quelle fonction ? Réponds en deux lignes, sans modifier de fichier." --model sonnet
Le calcul du score se trouve dans `app/quiz.py`, dans la fonction `calculer_score` (ligne 32).
Une génération courte, avec le cas limite dans la demande :
$ claude -p "Écris une fonction Python pourcentage(bonnes, total) qui renvoie le pourcentage arrondi à l'entier, et 0 quand total vaut 0. Réponds par le code seul, sans commentaire ni explication." --model sonnet
def pourcentage(bonnes, total):
if total == 0:
return 0
return round(bonnes / total * 100)
Le cas limite demandé est dans le code. Comparez avec calculer_score plus haut, écrit sans cette contrainte. Une relecture, enfin, sur ce même calculer_score :
$ claude -p "Relis la fonction calculer_score dans app/quiz.py et liste, en trois puces maximum, les cas limites qu'elle ne gère pas. Ne modifie rien." --model sonnet
Cas limites non gérés par `calculer_score` (app/quiz.py:32-36) :
- **Liste `reponses` vide ou entièrement sans `choix`** : `repondues` devient vide → division par
zéro (`ZeroDivisionError`) puisque `len(repondues)` vaut 0.
- **Réponses avec `question_id` inconnu de `questions_par_id`** : elles sont comptées dans
`repondues` (car `choix` n'est pas `None`) mais jamais dans `bonnes`, ce qui fausse le score à la
baisse sans signaler l'incohérence.
- **Doublons de `question_id`** dans `reponses` : rien n'empêche de compter plusieurs fois la même
question, ce qui peut gonfler ou fausser le score.
| Élément | Question de fait | Génération | Relecture |
|---|---|---|---|
| Contexte | « Dans QuizOps » | Signature imposée pourcentage(bonnes, total) |
« la fonction calculer_score dans app/quiz.py » |
| Tâche | « quel fichier, quelle fonction » | « écris une fonction » | « liste les cas limites qu'elle ne gère pas » |
| Contraintes | « sans modifier de fichier » | « arrondi à l'entier, 0 quand total vaut 0 » | « trois puces maximum », « ne modifie rien » |
| Format de réponse | « en deux lignes » | « le code seul, sans commentaire » | Une liste à puces |
calculer_score divise par le nombre de réponses reçues et rend 100 pour 5 bonnes réponses sur 10 questions.TP associé : TP 1 : Prise en Main de Claude Code sur un Projet Existant
➡️ Module suivant : Prise en Main de Claude Code