Comment relire les changements d’un agent de code
Un ensemble de changements regroupe tous les fichiers qu’un agent de code a ajoutés, modifiés ou supprimés pour une tâche. Un agent peut changer vingt fichiers en une minute, et tout lire avec le même soin ne tient pas.
Une relecture par le risque trouve les fichiers risqués et les lit en premier. Lire un petit diff à l’œil suffit pour un petit changement ; cette leçon trie tout un ensemble.
À la fin de cette leçon
Remarque
Je peux trier par risque l’ensemble de changements d’un agent de code avant de le lire.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Ensemble de changements | tous les fichiers changés pour une tâche |
| Dérive du périmètre | des changements au-delà de ce que la tâche demandait |
| Dépendance | un paquet dont le code a besoin pour tourner |
| Indicateur de risque | un signe qu’un changement demande de l’attention |
| Poids | le nombre de points que vaut un indicateur |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Cinq signes qu’un changement demande de l’attention
Un fichier que la tâche ne nommait pas est une dérive du périmètre. Un test supprimé, ou un test qui perd des lignes, peut cacher une fonction cassée. Un fichier de configuration modifié peut changer toute la construction.
Un très gros fichier nouveau est difficile à lire. Une nouvelle dépendance apporte du code que personne dans l’équipe n’a écrit. Chacun de ces signes est un indicateur de risque.
Exemple guidé : les indicateurs et leurs poids
| Indicateur | Quand il est levé | Poids |
|---|---|---|
| test supprimé | Un fichier de test est supprimé | 5 |
| test réduit | Un fichier de test perd plus de lignes qu’il n’en gagne | 3 |
| hors périmètre | La tâche ne nommait pas le fichier | 3 |
| config | package.json, package-lock.json ou ci.yml | 2 |
| gros | Plus de 200 lignes ajoutées et retirées | 1 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Noter, trier, puis lire
Additionnez les poids des indicateurs de chaque fichier pour lui donner un score. Triez les fichiers du plus gros score au plus petit, et gardez les égalités dans leur ordre d’arrivée.
Lisez ensuite les fichiers du haut en entier, ligne par ligne. Les fichiers du bas sont lus aussi, mais ils ne cachent plus les fichiers risqués. Un test supprimé renvoie tout le changement.
Une erreur fréquente
Attention
Beaucoup pensent qu’une relecture veut dire lire chaque ligne avec le même soin. Sur 400 lignes, les dix lignes risquées se cachent parmi les autres. Triez d’abord par risque, puis lisez en entier chaque fichier risqué.
Écrire l’assistant du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Les jetons d’une réponse, ou null si elle n’en donne pas.' 'function compter(donnees, nom) {' ' return donnees.usage && typeof donnees.usage[nom] === \"number\" ? donnees.usage[nom] : null;' '}' > model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’assistant du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function demander(messages, echantillon, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' const d = await res.json();' ' return { texte: d.choices[0].message.content, entree: compter(d, \"prompt_tokens\"), sortie: compter(d, \"completion_tokens\"), enregistre: false };' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' return { texte: echantillon, entree: null, sortie: null, enregistre: true };' ' }' '}' 'module.exports = { demander };' >> model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de relecture, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '// La tâche nommait deux fichiers. L’agent en a rendu sept.' 'const autorises = [\"boutique.js\", \"boutique.test.js\"];' 'const CONFIG = [\"package.json\", \"package-lock.json\", \"ci.yml\"];' 'const POIDS = { \"test supprimé\": 5, \"test réduit\": 3, \"hors périmètre\": 3, \"config\": 2, \"gros\": 1 };' > risk.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de relecture, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const changements = [' ' { chemin: \"boutique.js\", ajoutees: 12, retirees: 2, type: \"modifié\" },' ' { chemin: \"boutique.test.js\", ajoutees: 1, retirees: 9, type: \"modifié\" },' ' { chemin: \"prix.json\", ajoutees: 1, retirees: 1, type: \"modifié\" },' ' { chemin: \"ancien.test.js\", ajoutees: 0, retirees: 40, type: \"supprimé\" },' ' { chemin: \"package.json\", ajoutees: 1, retirees: 0, type: \"modifié\" },' ' { chemin: \"README.md\", ajoutees: 3, retirees: 0, type: \"modifié\" },' ' { chemin: \"utils/format.js\", ajoutees: 230, retirees: 0, type: \"ajouté\" },' '];' 'const avant = [\"outils-date\"];' 'const apres = [\"outils-date\", \"format-monnaie\"];' >> risk.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de relecture, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function indicateurs(c) {' ' const sortie = [];' ' const estTest = c.chemin.indexOf(\".test.\") >= 0;' ' if (estTest && c.type === \"supprimé\") sortie.push(\"test supprimé\");' ' if (estTest && c.type === \"modifié\" && c.retirees > c.ajoutees) sortie.push(\"test réduit\");' ' if (autorises.indexOf(c.chemin) < 0) sortie.push(\"hors périmètre\");' ' if (CONFIG.indexOf(c.chemin) >= 0) sortie.push(\"config\");' ' if (c.ajoutees + c.retirees > 200) sortie.push(\"gros\");' ' return sortie;' '}' 'function score(c) {' ' return indicateurs(c).reduce(function (somme, f) { return somme + POIDS[f]; }, 0);' '}' >> risk.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de relecture, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const ordre = changements.slice().sort(function (a, b) { return score(b) - score(a); });' ' ordre.forEach(function (c) {' ' console.log(score(c) + \" \" + c.chemin + (indicateurs(c).length ? \" : \" + indicateurs(c).join(\", \") : \"\"));' ' });' ' apres.filter(function (d) { return avant.indexOf(d) < 0; })' ' .forEach(function (d) { console.log(\"nouvelle dépendance : \" + d); });' ' const parti = changements.some(function (c) { return indicateurs(c).indexOf(\"test supprimé\") >= 0; });' ' console.log(parti ? \"renvoyer : un test a été supprimé\" : \"relire dans cet ordre\");' >> risk.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de relecture, partie 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' const q = \"En une phrase courte, pourquoi un relecteur doit-il regarder d’abord un fichier de test supprimé ?\";' ' const echantillon = \"Un test supprimé peut cacher une fonction cassée, car plus rien ne la contrôle.\";' ' const reponse = await demander([{ role: \"user\", content: q }], echantillon, 60);' ' console.log(\"le modèle dit : \" + reponse.texte);' '}' 'main();' >> risk.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer la relecture par risque
docker exec -w /home/node/project agent node risk.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Ce qu’affiche le Run
| Ligne | Ce qu’elle montre |
|---|---|
| 8 ancien.test.js : test supprimé, hors périmètre | Le fichier le plus risqué, lu en premier |
| 5 package.json : hors périmètre, config | Un fichier de configuration que la tâche ne nommait pas |
| 4 utils/format.js : hors périmètre, gros | Un gros fichier nouveau que personne n’a demandé |
| 3 boutique.test.js : test réduit | Le test nommé a perdu plus de lignes qu’il n’en a gagné |
| 3 prix.json : hors périmètre | Un fichier que la tâche ne nommait pas |
| 3 README.md : hors périmètre | Idem : la tâche ne parlait pas de la documentation |
| 0 boutique.js | Un fichier que la tâche nommait, sans aucun signe de risque, lu en dernier |
| nouvelle dépendance : format-monnaie | Du code nouveau venu de l’extérieur |
| renvoyer : un test a été supprimé | La décision pour tout l’ensemble |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.