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.

Code : matteogallo-ai/promptlang

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.

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.

Ce que devient un fichier .promptFichier .prompt, paramètres typés, sortie, modèle cible. Puis : Lecture, découpage en mots du langage,, puis arbre de la syntaxe. Issues : Générer, code typé, TypeScript, et Python ; Versions, casse-t-il, le code, appelant ? ; Relire, défauts, du texte, du prompt.Fichier .promptparamètres typés, sortie, modèle cibleLecturedécoupage en mots du langage,puis arbre de la syntaxeGénérercode typéTypeScriptet PythonVersionscasse-t-ille codeappelant ?Reliredéfautsdu textedu prompt
Le fichier lu sert trois usages indépendants. Le code généré vérifie chaque réponse du modèle contre le type de sortie déclaré ; le contrôle de version compare le prompt à sa version précédente ; la relecture signale les défauts d’écriture.

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

MesureValeurIC 95 %Lecture
Erreurs introduites exprès, repérées par les tests96,13 %Preuve : promptlang/docs/mutation/summary.jsonUn 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.json18 % – 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 contexte23Preuve : promptlang/docs/audit/2026-11-03-f1-neutral-audit.mdUn 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)

MesureLinter IAIC 95 %Tout signalerIC 95 %Lecture
Justesse des signalements44/217Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json15 % – 27 %45/360Preuve : promptlang/docs/SYSTEM_CARD.md9 % – 17 %Le gain sur un linter qui signale chaque catégorie sur chaque prompt est modeste.
Défauts trouvés44/45Preuve : promptlang/runs/live/linter-eval-2026-11-03T08-39-28-817Z.json88 % – 100 %45/45Preuve : promptlang/docs/SYSTEM_CARD.md92 % – 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.

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

    ADR 0002

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

    ADR 0003

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

    ADR 0004

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

Feuille de route