Brahim Bousnguar

Notes · Article

Mon générateur de README n’a pas le droit d’inventer

Par · · 3 min de lecture

Demandez à un LLM d’écrire un README et vous obtenez un texte long, sûr de lui et en partie inventé : des flags qui n’existent pas, des variables d’environnement que personne ne lit, une étape d’installation tirée d’un autre projet.

Alors dans vibetidy, ce n’est pas le LLM qui décide de ce qui est vrai. Un scanner lit le dépôt, et le modèle se contente de mettre ces faits vérifiés en prose.

Le plus drôle : pendant que je construisais un outil censé ne jamais annoncer ce qui n’existe pas, la CI m’a surpris à faire exactement ça. Deux fois.

Le scanner, c’est le produit

vibetidy readme commence par scanner le dépôt : le manifeste, les scripts, les dépendances, .env.example, les points d’entrée, l’arborescence, le README existant. Seuls ces faits partent vers le modèle, et le prompt le dit sans détour : ne jamais inventer une fonctionnalité, un flag, une variable d’environnement, une dépendance ou une version.

Quand les faits sont maigres, vous obtenez un README court et juste plutôt qu’un long README plausible. Et vous pouvez vérifier ce que le modèle verrait avant de dépenser le moindre token :

npx vibetidy readme --print-context

Pas besoin de clé d’API pour ça. La commande affiche les faits scannés et le prompt complet, puis s’arrête.

Le dépôt

  • manifeste
  • scripts
  • dépendances
  • .env.example
  • points d’entrée
  • arborescence
  • README existant

Le scanner

Faits vérifiés

La seule chose que voit le modèle

Le modèle

Les met en prose

Ne jamais inventer

  • une fonctionnalité
  • un flag
  • une variable d’env
  • une dépendance
  • une version

README.mdUn LLM livré à lui-même

un flag qui n’existe pas

une variable d’env que nul ne lit

une étape tirée d’un autre projet

Long, sûr de lui, en partie inventé

README.mdvibetidy, faits maigres

Court, et juste

Sans clé--print-context affiche les faits scannés et le prompt complet, puis s’arrête.

Le modèle n’écrit qu’à partir de ce que le scanner a trouvé. Les deux esquisses de README schématisent leur longueur : ce ne sont pas de vrais fichiers.

Premier bug : un flag documenté, jamais enregistré

issue-check --help listait --no-changelog. Le tableau des options du README aussi. L’utiliser se soldait par Unknown option '--no-changelog'.

Le flag n’avait jamais été enregistré dans le parseur. Le parseArgs de Node ne reconnaît un préfixe --no- qu’avec allowNegative, qui demande un Node plus récent que le >=20.10.0 annoncé par le paquet. Il est donc devenu un simple --skip-changelog.

Le correctif tenait en une ligne. Le plus intéressant, c’est que rien ne l’avait repéré : la doc avait l’air juste, le code paraissait plausible, tous les tests passaient. J’ai donc ajouté un test pour toute cette classe de bugs. Il extrait chaque flag de la sortie --help de chaque commande et vérifie que le parseur l’accepte :

for (const flag of flagsIn(help)) {
  const res = runCli([command, flag, 'x', '--help']);
  assert.notEqual(res.status, 2, `${flag} is documented but rejected`);
}

Pour m’assurer qu’il mord, j’ai glissé un faux --phantom-flag dans le texte d’usage. La suite a échoué avec le message de rejet exact.

  1. issue-check --help

    --no-changelog

    le listait

  2. Options du README

    --no-changelog

    le listaient

  3. Le parseur

    Unknown option '--no-changelog'

    ne l’a jamais enregistré

  1. parseArgs ne reconnaît --no- qu’avec allowNegative
  2. Il faut pour ça un Node plus récent que >=20.10.0
  3. Il est donc devenu --skip-changelog

Le nouveau test : chaque flag du --help de chaque commande passe par le parseur

--skip-changelogaccepté

--phantom-flagis documented but rejected

Glissé exprès dans le texte d’usage : la suite a échoué.

Deux endroits documentaient le flag, et le parseur n’en avait jamais entendu parler. Le test confronte désormais chaque flag documenté au parseur : toute la classe de bugs d’un coup.

Deuxième bug : les tests n’ont jamais tourné sur Node 20

Ouvrir cette PR a fait passer la CI au rouge sur main : 5 jobs sur 9.

npm test lançait node --test "test/**/*.test.js". Les globs ne sont arrivés dans node --test qu’avec Node 21. Sur Node 20, le motif entre guillemets est pris pour un chemin littéral, donc chaque job 20.x échouait avec Could not find .../test/**/*.test.js. La suite n’avait jamais tourné sur la version de Node promise par engines.

Le correctif est presque drôle : un simple node --test, sans aucun motif. La découverte par défaut fonctionne depuis Node 18. La CI est passée au vert sur les neuf jobs (Ubuntu, macOS et Windows, croisés avec Node 20, 22 et 24).

Même classe que le premier bug : quelque chose d’annoncé qui ne marchait pas. C’est grâce à la matrice de 9 jobs que j’ai trouvé les deux.

Comment chaque version de Node lit la commande de test

18

19

20

21

22

23

24

Un simple node --test trouve les tests

Glob lu comme un chemin littéral

Le glob marche

un job de CI

Avant, sur main

node --test "test/**/*.test.js"

5 sur 9 jobs au rouge

Chaque job 20.x : Could not find .../test/**/*.test.js

Après

node --test

9 sur 9 jobs au vert

Pourquoi le glob cassait sur Node 20, et le correctif. Les carrés rouges comptent les jobs en échec sur main ; ils ne les placent pas dans la matrice.

npm a refusé un nom qui semblait libre

L’outil s’appelait d’abord tidyrepo. npm view tidyrepo renvoyait une 404, donc le nom semblait disponible.

Puis npm publish a échoué avec une 403. Le contrôle de similarité de npm retire la ponctuation, donc tidyrepo entrait en collision avec un tidy-repo existant. Une 404 ne prouve pas qu’un nom est libre : vérifiez aussi les variantes avec tiret et avec underscore.

Il s’appelle vibetidy maintenant, et franchement, ça lui va mieux.

  1. npm view tidyrepo

    404

    Semble libre

  2. npm publish

    403

    Refusé

Le contrôle de similarité de npm retire la ponctuation

Déjà sur npm

tidy-repo

tidyrepo

Le mien

tidyrepo

tidyrepo

Même nom sans ponctuation

Une 404 ne prouve rien. Vérifiez aussi

  • tidyrepo
  • tidy-repo
  • tidy_repo

Désormaisvibetidy

La vérification du nom, étape par étape. Un nom libre doit le rester une fois sa ponctuation retirée.

Essayez-le

C’est sous licence MIT, sans aucune dépendance à l’exécution, et ça marche avec OpenAI, OpenRouter, Anthropic, Groq, DeepSeek, Together ou un Ollama local :

npx vibetidy readme --print-context      # see the facts, no key needed
npx vibetidy readme                      # generate, review the diff, confirm
npx vibetidy issue-check --install-hook  # nag on feature commits with no issue
  1. 1Voir les faits

    npx vibetidy readme --print-context

  2. 2Générer, relire le diff, confirmer

    npx vibetidy readme

  3. 3Rappel à l’ordre sur les commits de fonctionnalité sans issue

    npx vibetidy issue-check --install-hook

Marche avec

  • OpenAI
  • OpenRouter
  • Anthropic
  • Groq
  • DeepSeek
  • Together
  • Ollama, en local

MITlicence

0dépendance à l’exécution

Les trois commandes de la note, dans l’ordre où vous les lanceriez. Les petits dessins sont schématiques.

S’il écrit quelque chose que votre dépôt ne confirme pas, c’est un bug. Ouvrez une issue sur GitHub.

Texte brut: vibetidy-grounded-readme.md · Flux Atom · Toutes les notes

Contact

Écrivez-moi.

b.bousnguar@gmail.com

Intégration SAP Commerce Cloud, MuleSoft et Salesforce · Nantes · FR / EN