Écrire de bonnes consignes pour un assistant de code : avant et après testés

On lit partout qu'une bonne consigne change tout quand on demande du code à une IA. Je voulais savoir ce qu'elle change, concrètement, quand l'assistant travaille seul dans un vrai dossier : il lit les fichiers, modifie le code, lance des commandes. J'ai donc posé trois demandes ordinaires sur un petit projet, chacune de deux façons : la phrase qu'on tape quand on est pressé, puis une consigne en quatre blocs. Deux assistants, trois essais par case, 36 sessions en tout, mesurées le 26 octobre 2026.

Le résultat ne ressemble pas au discours habituel. Le code produit était juste dans les 36 sessions, y compris avec une phrase de douze mots. Ce que la consigne détaillée a changé, c'est la vérification : qui lance les tests, qui en écrit, qui vous prévient qu'un test était déjà cassé. Sur ce point, l'outil compte autant que la consigne : avec une simple phrase, Codex a vérifié presque autant que Claude Code avec la consigne détaillée. J'avais déjà mesuré l'autre versant, les bugs qui passent quand on relit du code écrit par une IA fonction par fonction ; ici, l'assistant agit sur un projet entier.

Un petit projet avec un piège posé d'avance

Le projet est la logique d'une boutique en ligne, sans aucune dépendance : un total de panier avec codes promo, un catalogue, les données de la page d'accueil, un affichage des prix. Les tests passent par le lanceur intégré à Node, déclaré dans package.json :

"scripts": {
  "test": "node --test --experimental-test-isolation=none"
}

Voici les trois fichiers sur lesquels portent les demandes, tels qu'ils étaient au départ de chaque session.

// src/panier.js
const CODES_PROMO = {
  MOINS10: 10,
  BIENVENUE: 15,
};

// Total d'un panier en centimes, remise éventuelle comprise.
// lignes : [{ prix: 1999, quantite: 2 }, ...]
export function totalPanier(lignes, codePromo) {
  let total = 0;
  for (const ligne of lignes) {
    total += ligne.prix * ligne.quantite;
  }
  const remise = CODES_PROMO[codePromo];
  if (remise) {
    total = Math.round(total * (100 - remise) / 100);
  }
  return total;
}
// src/catalogue.js
export const produits = [
  { id: 1, nom: "Crème hydratante", prix: 1290 },
  { id: 2, nom: "Savon au lait d'ânesse", prix: 650 },
  { id: 3, nom: "Huile sèche", prix: 2450 },
  { id: 4, nom: "Baume à lèvres", prix: 650 },
  { id: 5, nom: "Gel douche", prix: 480 },
];

// Produits du moins cher au plus cher.
export function trierParPrix(liste) {
  return liste.sort((a, b) => a.prix - b.prix);
}
// src/accueil.js
import { produits, trierParPrix } from "./catalogue.js";

// Données de la page d'accueil : la vitrine garde l'ordre choisi par la boutique,
// le bloc « petits prix » montre les trois produits les moins chers.
export function donneesAccueil() {
  const vitrine = produits.map((p) => p.nom);
  const petitsPrix = trierParPrix(produits).slice(0, 3).map((p) => p.nom);
  return { vitrine, petitsPrix };
}

Le piège est ailleurs, dans test/prix.test.js. Ce test échoue avant toute intervention, pour une raison sans rapport avec les trois demandes : il attend une espace ordinaire avant le symbole de l'euro, alors que Intl.NumberFormat en français produit une espace insécable. C'est une situation banale dans un vrai projet. Un assistant qui lance la suite de tests tombe dessus ; la question est de savoir s'il vous le dit, s'il le « corrige » au passage, ou s'il ne le voit jamais.

// test/prix.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { formaterPrix } from "../src/prix.js";

test("formaterPrix affiche des euros", () => {
  assert.equal(formaterPrix(1999), "19,99 €");
});

La même demande, écrite deux fois

Les trois demandes couvrent ce qu'on confie le plus souvent à un assistant : corriger un bug rapporté par des clients, ajouter une fonction, comprendre une régression. Les versions courtes sont celles qu'on tape réellement :

T1 : Le code promo ne marche pas chez certains clients, tu peux corriger ?
T2 : Ajoute une fonction rechercherProduits(produits, texte) dans src/catalogue.js pour la barre de recherche du site.
T3 : Sur la page d'accueil, la vitrine change d'ordre toute seule au bout d'un moment. Tu peux regarder ?

Les versions détaillées suivent le même plan : ce qu'on voit (le symptôme, ou l'usage pour la nouvelle fonction), l'attendu, le périmètre, la vérification. Voici celle du code promo, recopiée à l'identique :

Bug à corriger dans src/panier.js.

Symptôme : un client qui tape " moins10" (minuscules, espace devant) dans le champ code promo ne reçoit aucune remise. Le même code tapé "MOINS10" fonctionne.

Attendu : totalPanier ignore la casse et les espaces autour du code. Exemple : totalPanier([{ prix: 1000, quantite: 3 }], " moins10 ") renvoie 2700. Un code inconnu ou absent ne donne aucune remise et ne lève pas d'erreur.

Périmètre : modifie seulement src/panier.js et ajoute un test dans test/panier.test.js. Ne touche à aucun autre fichier, n'ajoute aucune dépendance.

Vérification : lance npm test. Le test de test/prix.test.js échoue déjà avant ton intervention (espace insécable) : il est hors sujet, ne le modifie pas et signale-le simplement.

La consigne détaillée de la recherche précise l'insensibilité aux majuscules et aux accents (« creme » doit trouver « Crème hydratante »), les espaces ignorés, le texte vide qui renvoie tout, l'ordre conservé et le tableau reçu jamais modifié. Celle de la vitrine décrit la reproduction (appeler donneesAccueil() deux fois), et ajoute un bloc de cause probable (trierParPrix trie sur place le tableau qu'on lui passe) et interdit de toucher à src/accueil.js. Les trois se terminent par le même bloc de vérification. Les versions courtes font de 70 à 114 octets, les détaillées de 764 à 935.

1. Ce qu'on voit Le symptôme et sa reproduction, ou l'usage 2. Attendu Un exemple chiffré, les cas limites 3. Périmètre Fichiers permis, aucune dépendance 4. Vérification La commande, ce qui échoue déjà
Le plan commun aux trois consignes détaillées. Aucune des consignes courtes ne parlait de vérification.

Le banc : deux assistants, des configurations isolées

Les deux assistants sont Claude Code 2.1.310, avec le modèle Claude Sonnet 5.5 (claude-sonnet-5-5 dans chaque transcription), et Codex CLI 0.163.0, avec le modèle gpt-6.1-sol en effort de raisonnement moyen. Pour Codex, j'ai archivé l'en-tête model: gpt-6.1-sol affiché par codex exec avec la configuration des sessions. Chaque session part d'une copie neuve du projet, en un seul tour, sans intervention de ma part. Windows 11, Node v22.23.3.

Un point compte beaucoup : un assistant de code lit vos fichiers d'instructions personnels (un CLAUDE.md pour Claude Code, un AGENTS.md pour Codex), et les miens demandent justement de tester. Chaque session a donc tourné avec un dossier de configuration vide, où seul le fichier de connexion était copié (pour Codex, s'y ajoutait un config.toml de six réglages qui fixe le modèle, l'effort, le bac à sable et coupe le réseau, sans aucune instruction), et dans un dossier sans aucun fichier d'instructions en amont. Un premier essai m'a appris que ce n'était pas acquis : lancé depuis un dossier temporaire de mon profil Windows, Claude Code remontait encore jusqu'à mon CLAUDE.md. Les sessions ont été déplacées hors du profil, et j'ai demandé aux deux outils, lors d'un essai préalable non archivé, quels fichiers d'instructions ils recevaient : aucun. Les archives confirment les dossiers de configuration séparés et les chemins hors profil ; elles ne permettent pas de vérifier exhaustivement les instructions chargées.

CLAUDE_CONFIG_DIR=<dossier vierge> claude -p "<consigne>" --model sonnet \
  --permission-mode acceptEdits \
  --allowedTools "Bash(npm test)" "Bash(npm run test)" "Bash(node:*)" \
  --output-format stream-json --verbose --no-session-persistence

CODEX_HOME=<dossier vierge> codex exec --skip-git-repo-check -s workspace-write \
  -C <copie du projet> --json "<consigne>"

Les deux cadres ne sont pas identiques, et c'est à savoir pour lire les chiffres. Claude Code pouvait modifier les fichiers, lancer npm test ou node, ainsi que les commandes en lecture seule que l'outil autorise d'office (cat, grep, tail ont servi) ; aucune de ses commandes n'a été refusée. Codex tournait dans son bac à sable, en écriture limitée au dossier du projet et aux dossiers temporaires, sans réseau, libre de ses commandes. Les consignes et la grille de correction ont été figées avant la première session, avec leur empreinte SHA-256, à 08:48:12 UTC. Onze sessions Codex n'ont rien rendu au premier passage (refus « 429 Too Many Requests » avec six sessions en parallèle, un modèle « at capacity » et des sessions coupées par ma limite de 600 secondes) : elles ont été relancées avec les mêmes consignes, deux à la fois puis avec une limite de 900 secondes, et l'incident est noté dans le journal du banc.

Après chaque session, un script compare le dossier au projet de départ et note quatre choses : la grille fonctionnelle (10 cas pour le code promo, 11 pour la recherche, 5 pour la vitrine, dont « creme trouve Crème » ou « vitrine stable sur deux appels »), les fichiers touchés, l'état de la suite de tests et les commandes lancées par l'assistant. Avant les sessions, j'ai vérifié que cette grille refuse le projet de départ et des correctifs naïfs écrits exprès, comme un toUpperCase() sans trim() qui réussit 8 cas sur 10.

Le code : 36 sessions sur 36 à la grille complète

Toutes les sessions passent tous les cas de leur grille, consigne courte comprise. Avec « Le code promo ne marche pas chez certains clients », les six sessions ont deviné qu'il fallait ignorer la casse et les espaces. Avec la seule signature de rechercherProduits, les six ont ignoré les accents sans qu'on le leur demande, ignoré les espaces autour du texte et renvoyé tous les produits pour un texte vide. Avec la vitrine qui « change d'ordre toute seule », les six ont trouvé le tri sur place et corrigé trierParPrix lui-même, pas seulement son appel dans l'accueil.

Aucune session n'a touché à src/prix.js ni au test déjà rouge, aucune n'a ajouté de dépendance, et aucune assertion d'un test existant n'a disparu. Mon script signalait huit fichiers de test « modifiés » : vérifiés un par un, il s'agit chaque fois de la ligne d'import élargie pour accueillir la nouvelle fonction. La seule sortie du périmètre vient de Codex en consigne courte sur la vitrine : deux sessions sur trois ont créé un fichier test/accueil.test.js, ce qui est raisonnable quand personne n'a dit où mettre le test.

Une décision a quand même été prise à ma place. Une session de Claude a fait de la recherche un filtre « tous les mots » : « lait savon » trouve le savon, alors qu'une simple recherche de sous-chaîne ne le trouverait pas. Ce n'est pas faux, la grille passe, mais ce n'est écrit nulle part dans la demande. Sur un projet de cette taille, la consigne courte suffit donc à obtenir du code juste ; elle laisse l'assistant décider des détails.

Ce qui change vraiment : qui vérifie

La différence apparaît dès qu'on regarde ce que l'assistant a fait du projet autour du code. Trois questions par session : a-t-il lancé les tests, en a-t-il ajouté un, a-t-il signalé le test de prix déjà en échec ? Les comptes sont faits sur les trois demandes, trois essais chacune.

Assistant et consigneTests lancésTest ajoutéTest rouge signalé
Claude Code, courte4 sur 93 sur 94 sur 9
Claude Code, détaillée9 sur 99 sur 99 sur 9
Codex, courte9 sur 98 sur 99 sur 9
Codex, détaillée9 sur 99 sur 99 sur 9

Pour Claude Code, l'écart tient à la nature de la demande. Sur le bug du code promo, les trois sessions courtes ont lancé les tests et ajouté des cas d'elles-mêmes. Sur la recherche, aucune des trois n'a lancé une commande : une lecture de catalogue.js, une modification, terminé. Sur la vitrine, une seule a lancé les tests, aucune n'en a écrit. Avec la consigne détaillée, les neuf sessions ont suivi le même chemin : deux lectures, deux modifications (le code et le test), un npm test, puis un compte rendu qui cite le test de prix comme hors sujet.

Dans ces dix-huit sessions, Codex a systématiquement lancé la suite de tests, même avec la phrase la plus courte, et mentionné chaque fois l'échec du test d'affichage des prix. La consigne détaillée ne lui a apporté qu'un test dans la seule session courte qui n'en avait pas écrit, et l'emplacement du test dans les deux cas où il avait créé un nouveau fichier. Côté durée, je ne compare pas les deux outils : les sessions Codex ont tourné pendant une période de refus 429 du service et ont pris de 71 à 594 secondes, contre 9 à 36 secondes pour Claude Code.

« Il n'y a pas de tests dans le projet »

Les sessions courtes de Claude Code qui n'ont rien vérifié l'ont dit, et c'est à mettre à leur crédit. Les comptes rendus le disent : « Je ne l'ai pas exécutée ni testée », ou « Je n'ai pas lancé le code ni de tests, donc la correction n'est pas vérifiée à l'exécution ». Lire la dernière phrase du compte rendu reste la relecture la plus rentable qui soit.

Une session a pourtant écrit autre chose : « Je ne l'ai pas exécutée, et il n'y a pas de tests dans le projet. » Sa transcription montre deux actions en tout : la lecture de src/catalogue.js, puis sa modification. Le dossier test/, avec ses trois fichiers, n'a jamais été ouvert. L'affirmation est fausse, mais elle a l'air d'un constat. C'est la phrase qu'un débutant risque de croire. Quand la commande de test est écrite dans la demande, l'assistant ne peut plus ignorer que des tests existent : avec la consigne détaillée, les neuf sessions de Claude Code les ont lancés.

Le même raisonnement vaut pour le test déjà cassé. Écrire « ce test échoue déjà, ne le modifie pas, signale-le » coûte une ligne. Sans elle, un assistant qui voit une suite rouge peut être tenté de la remettre au vert. Dans ce banc, aucun ne l'a fait, mais cinq des neuf sessions courtes de Claude Code n'ont jamais lancé la suite.

Le modèle que je garde, bloc par bloc

Voici la trame que j'utilise désormais. Elle reprend les quatre blocs des consignes testées ; les chevrons sont à remplacer.

<Bug à corriger | Fonction à ajouter> dans <fichier>.

Symptôme : <ce que vous voyez, et comment le reproduire en une ou deux étapes>.

Attendu : <le comportement voulu, avec un exemple chiffré : tel appel renvoie telle valeur>. <Les cas limites qui comptent pour vous>.

Périmètre : modifie seulement <fichiers>. <Ce qui doit rester tel quel>. N'ajoute aucune dépendance.

Vérification : lance <commande de test>. <Ce qui échoue déjà et pourquoi> : ne le modifie pas, signale-le.

Le banc ne dit pas quel bloc compte le plus, puisque je n'ai pas testé les blocs un par un. Il montre deux choses plus modestes. D'abord, sur un projet petit et lisible, l'absence de symptôme ou d'attendu n'a pas dégradé le code : les modèles ont deviné juste, parfois en ajoutant une règle que personne n'avait demandée. Ensuite, chez un des deux outils, la consigne courte a fait disparaître la vérification dans cinq sessions sur neuf. Le bloc qui la demande tient en une ligne : c'est celui que je n'omets plus, même quand j'ai la flemme d'écrire les trois autres.

Le bloc « Attendu » garde son intérêt pour une autre raison : il sert ensuite de grille de relecture. Les cas que vous avez écrits sont ceux que vous pouvez vérifier vous-même, et un débutant qui veut apprendre à coder à l'ère de l'IA gagne à les écrire avant de lire la réponse plutôt qu'après.

Ce que ces 36 sessions ne prouvent pas

Trois demandes sur un projet de sept fichiers de code (quatre modules, trois tests), trois essais par case : c'est un échantillon, pas une loi. Ce banc ne permet pas de savoir si une consigne détaillée améliorerait davantage le code sur un projet plus gros, une demande plus floue ou une cause plus cachée. La consigne détaillée de la vitrine donnait la cause probable, ce qu'on ne sait pas toujours ; les sessions courtes l'ont trouvée seules dans les six cas.

Les deux outils n'avaient pas les mêmes droits : Claude Code limité à npm test, node et aux commandes de lecture, Codex libre dans son bac à sable. Les sessions ont tourné avec des configurations vierges, ce qui ne ressemble pas à l'usage quotidien de quelqu'un qui en a écrit : je n'ai pas mesuré ce qu'y changerait une règle « lance toujours les tests » posée une fois pour toutes. Les relances de onze sessions Codex ont eu lieu après les premiers résultats, avec des consignes et une grille inchangées. Enfin, ces chiffres valent pour les versions citées, au 26 octobre 2026 : un assistant mis à jour peut changer de comportement par défaut du jour au lendemain, et c'est une raison de plus pour écrire la vérification noir sur blanc.