No. 02 · Étude de cas
PromptLang
Un langage pour écrire les consignes données aux modèles d’IA (les prompts) comme du code : typées, compilées vers TypeScript ou Python, versionnées.
En bref
- Ce que ça fait
- Il transforme un prompt en fichier vérifié, compilé en code TypeScript ou Python, et signale quand une nouvelle version casse le code de ceux qui l’utilisent.
- Ce qui est mesuré
- La solidité des tests de la lecture du langage et du contrôle de version, et la justesse d’un relecteur automatique, le linter IA, qui pointe les défauts d’écriture des prompts.
- Défaut principal
- La plupart des signalements du linter IA sont faux, à peine mieux que tout signaler. Ce que j’en ai fait : je l’ai mesuré contre ce point de comparaison, publié, et le linter est présenté comme une aide au relecteur humain, pas comme un contrôle.
96,13 %Preuve : promptlang/docs/mutation/summary.json
des erreurs introduites exprès dans la lecture du langage et le contrôle de version, repérées par les tests
44/45Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json
défauts conçus trouvés par le linter IA ; tout signaler les trouve tous
12/40Preuve : promptlang/evals/linter/findings-review/score.json
signalements du linter IA jugés justes à la relecture humaine
Le problème
En trois phrases, pourquoi ce projet existe.
Les prompts vivent comme des bouts de texte éparpillés dans le code : rien ne vérifie leurs entrées, rien ne les teste, rien ne les versionne. Quand un prompt casse en production, on l’apprend par un client. PromptLang en fait des fichiers vérifiés, compilés, testés et versionnés comme du code.
Ce que j’ai construit
Ce que devient un fichier de prompt, de sa lecture aux trois usages qui en sortent.
Un fichier .prompt déclare ce que le prompt reçoit, ce qu’il doit rendre et le modèle visé. Le compilateur en tire une fonction typée. Le contrôle de version compare ce contrat avec la version précédente dans Git et refuse un changement qui casse le code appelant sans un numéro de version majeur. Le linter IA relit le texte du prompt avant la relecture humaine. Le paquet@promptlang/core est publié sur npm, le registre des paquets JavaScript, et Praxis l’utilise pour lire ses propres prompts.
Mesures
Ce que valent les tests et le linter IA, mesurés contre un point de comparaison très simple : tout signaler.
Pour mesurer les tests, on introduit exprès de petites erreurs dans le code (une condition inversée, une valeur changée) et on regarde si les tests échouent : ils en repèrent 96,13 %Preuve : promptlang/docs/mutation/summary.json, sur la lecture du langage et le contrôle de version seulement.
Sur des prompts de test aux défauts connus, le linter IA en trouve 44/45Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json. Mais un linter qui signale tout, sur chaque prompt, les trouve tous : 45/45Preuve : promptlang/docs/SYSTEM_CARD.md. La différence est dans la justesse, et l’écart est modeste : 44/217Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json des signalements du linter visent un vrai défaut, contre 45/360Preuve : promptlang/docs/SYSTEM_CARD.md pour tout signaler, et à la relecture humaine 12/40Preuve : promptlang/evals/linter/findings-review/score.json seulement sont justes.
Détail des mesures
Une proportion porte son intervalle de confiance à 95 % (IC) : la plage où se trouve probablement la vraie valeur, large quand l’échantillon est petit.
Tests, linter IA, audit
| Mesure | Valeur | IC 95 % | Lecture |
|---|---|---|---|
| Erreurs introduites exprès, repérées par les tests | 96,13 %Preuve : promptlang/docs/mutation/summary.json | Un décompte de toutes les erreurs introduites dans le code couvert, pas un échantillon. | Exécution locale, hors intégration continue, sur la lecture du langage et le contrôle de version seulement : ni le compilateur ni l’exécution. |
| Précision du linter IA (revue humaine) | 12/40Preuve : promptlang/evals/linter/findings-review/score.json | 18 % – 46 % | Le défaut principal : à la relecture humaine, la plupart des signalements sont faux. Ne se distingue pas de la borne déjà publiée. |
| Constats de l'audit par un agent sans contexte | 23Preuve : promptlang/docs/audit/2026-11-03-f1-neutral-audit.md | Un décompte, pas une estimation. | Audit du dépôt par un agent IA qui ne connaissait pas le projet : des défauts, et un constat de contexte. Chaque défaut est corrigé ou réfuté par écrit. |
Linter IA contre « tout signaler » (prompts de test)
| Mesure | Linter IA | IC 95 % | Tout signaler | IC 95 % | Lecture |
|---|---|---|---|---|---|
| Justesse des signalements | 44/217Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json | 15 % – 27 % | 45/360Preuve : promptlang/docs/SYSTEM_CARD.md | 9 % – 17 % | Le gain sur un linter qui signale chaque catégorie sur chaque prompt est modeste. |
| Défauts trouvés | 44/45Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json | 88 % – 100 % | 45/45Preuve : promptlang/docs/SYSTEM_CARD.md | 92 % – 100 % | Tout signaler trouve tout : ce chiffre seul ne distingue pas le linter du point de comparaison. |
La justesse sur les prompts de test compte les signalements qui tombent sur un défaut conçu ; la relecture humaine relit des signalements un par un. Les deux disent la même chose : le linter IA aide le relecteur, il ne le remplace pas (fiche du système).
Ce qui n’a pas marché, et ce que j’ai fait
Ce qu’un audit sans complaisance a trouvé, et ce que chaque constat est devenu.
J’ai fait auditer le dépôt par un agent IA qui ne connaissait rien du projet, chargé de chercher la faille. Son verdict : les promesses de la page de présentation du projet (le README) dépassaient ce que le code faisait vraiment. Ce que j’en ai fait, constat par constat, est dans le suivi de l’audit.
Un linter IA déclaré livré, sans point de comparaison
Cette page présentait le linter comme livré alors que sa justesse était faible et que rien ne disait ce qu’elle valait. Un linter qui signale tout trouve tous les défauts conçus : en trouver beaucoup ne prouvait presque rien. J’ai publié sa justesse à côté de ce point de comparaison (#29), puis fait relire des signalements à la main ; le linter est décrit comme une aide au relecteur, pas comme un contrôle.
Une sortie typée jamais vérifiée
Le code généré promettait une réponse du modèle conforme à un type, sans le vérifier. Il la vérifie maintenant, en TypeScript comme en Python, et signale une erreur sinon ; les deux langages tournent sur les mêmes réponses et doivent s’accorder (#27). La correction a commencé par un test qui échouait.
Des tests mesurés là où le risque n’était pas
La mesure des tests par erreurs introduites ne porte que sur la lecture du langage et le contrôle de version. L’audit a trouvé des défauts hors de ce périmètre (compilateur, exécution, ligne de commande, analyse du texte, plafond de dépense), et un dans le contrôle de version même : renommer un type exporté était classé comme un simple correctif (patch), le plus petit changement de version. C’était un choix écrit dans la note de décision, et aucune erreur introduite ne révèle une règle mal choisie. Le rapport liste désormais ce qu’il ne couvre pas (rapport), et ce renommage exige une version majeure (#25).
Trois décisions
Trois choix d’architecture, chacun écrit dans une note de décision (ADR) avec ses raisons et ses conséquences : de quoi juger le raisonnement, pas seulement le résultat.
Ce qui compte comme contrat d’un prompt
Ce que le prompt reçoit (noms, types, valeurs facultatives), ce qu’il rend et le modèle visé, sous une forme qui ignore l’ordre des paramètres et des valeurs d’une liste fermée. Un amendement issu de l’audit y ajoute les noms des types exportés : renommer un type que le code appelant importe exige une version majeure.
Comment le contrôle de version trouve la version précédente
La version précédente vient de Git : la référence donnée, sinon
origin/main, sinon la dernière étiquette de version posée dans Git (un tag) ; la version courante est celle des fichiers sur le disque, donc le contrôle tourne avant l’enregistrement comme en intégration continue. Le niveau exigé est celui du changement le plus grave, et un fichier illisible dans la version précédente est une erreur, jamais un fichier nouveau.Le coût approuvé est un plafond de dépense
Chaque exécution payante affiche son coût estimé et demande une approbation ; cette estimation devient un plafond. Avant chaque appel, le client réserve le coût maximal de l’appel ; un appel qui dépasserait le plafond n’est pas envoyé. La note écrit aussi ce que le plafond ne garantit pas : un texte plus coûteux à traiter que prévu dépasse l’estimation.
Comment c’est construit
Pour un lecteur technique : la pile, les outils et le modèle du linter IA.
TypeScript strict sur Bun. Tests bun test, tests générés au hasard (fast-check) sur la lecture du langage et le contrôle de version, erreurs introduites avec Stryker, Biome, knip. Exécution chez plusieurs fournisseurs : Anthropic, OpenAI, Ollama ou un client fourni. Le linter IA appelle claude-haiku-5-5. Architecture : docs/architecture.md.
La suite
Ce qui manque, d’après la feuille de route du projet ; rien n’y est planifié.
- Exécuter les tests écrits dans les fichiers de prompt : ils sont lus, pas exécutés.
- Mesurer les tests du compilateur et de l’exécution par erreurs introduites, hors du périmètre mesuré aujourd’hui.
- Un linter IA chez d’autres fournisseurs que Claude, chacun avec sa propre mesure.
- La réponse affichée au fil de l’eau, et un support d’éditeur (coloration, complétion).