Brahim Bousnguar

Notes · Article

My README generator isn't allowed to make things up

By · · 3 min read

Listen · 3:57 · read by an AI voice

Listen to this article0:00 / 3:57

Read by an AI voice · MP3

Ask an LLM to write a README and you get something long, confident and partly invented: flags that don't exist, env vars nobody reads, a setup step from a different project.

So in vibetidy the LLM doesn't get to decide what's true. A scanner reads the repo, and the model only turns those verified facts into prose.

Funny thing: while building a tool about not advertising what isn't real, CI caught me doing exactly that. Twice.

The scanner is the product

vibetidy readme scans the repository first: the manifest, scripts, dependencies, .env.example, entry points, the folder tree, the existing README. Only those facts go to the model, and the prompt says it plainly: never invent a feature, a flag, an environment variable, a dependency or a version.

When the facts are thin, you get a short README that's right instead of a long one that's plausible. And you can check what the model would see before spending a token:

npx vibetidy readme --print-context

No API key needed for that. It prints the scanned facts and the full prompt, and exits.

The repo

  • manifest
  • scripts
  • dependencies
  • .env.example
  • entry points
  • folder tree
  • existing README

The scanner

Verified facts

The only thing the model sees

The model

Turns them into prose

Never invent

  • a feature
  • a flag
  • an env var
  • a dependency
  • a version

README.mdAn LLM on its own

a flag that doesn't exist

an env var nobody reads

a setup step from a different project

Long, confident, partly invented

README.mdvibetidy, thin facts

Short, and right

No key--print-context prints the scanned facts and the full prompt, and exits.

The model only writes from what the scanner found. The two README sketches are a schematic of length, not real files.

Bug one: a flag I documented but never registered

issue-check --help listed --no-changelog. The README's options table listed it too. Using it exited with Unknown option '--no-changelog'.

The flag was never registered in the parser. Node's parseArgs only honours a --no- prefix with allowNegative, which needs a newer Node than the >=20.10.0 the package advertises. So it became a plain --skip-changelog.

The fix was one line. The interesting part is that nothing caught it: the docs looked right, the code read plausibly, every test passed. So I added a test for the whole class of bug. It pulls every flag out of each command's --help output and asserts the parser accepts it:

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

To make sure it bites, I planted a fake --phantom-flag in the usage text. The suite failed with the exact rejection message.

  1. issue-check --help

    --no-changelog

    listed it

  2. README options table

    --no-changelog

    listed it

  3. The parser

    Unknown option '--no-changelog'

    never registered it

  1. parseArgs honors --no- only with allowNegative
  2. That needs a newer Node than >=20.10.0
  3. So it became --skip-changelog

The new test: every flag in each command's --help, through the parser

--skip-changelogaccepted

--phantom-flagis documented but rejected

Planted in the usage text on purpose: the suite failed.

Two places documented the flag and the parser had never heard of it. The test now checks every documented flag against the parser, the whole class of bug at once.

Bug two: the tests never ran on Node 20

Opening that PR turned CI red on main: 5 legs out of 9.

npm test ran node --test "test/**/*.test.js". Glob support in node --test only landed in Node 21. On Node 20 the quoted pattern is taken as a literal path, so every 20.x leg failed with Could not find .../test/**/*.test.js. The suite had never run on the Node version engines promised.

The fix is almost funny: plain node --test, with no pattern at all. Default discovery has worked since Node 18. CI went green on all nine legs (Ubuntu, macOS and Windows, times Node 20, 22 and 24).

Same class as bug one: something advertised that didn't work. The 9-leg matrix is why I found both.

How each Node version reads the test command

18

19

20

21

22

23

24

Plain node --test finds the tests

Glob read as a literal path

Glob works

a CI leg

Before, on main

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

5 of 9 legs red

Every 20.x leg: Could not find .../test/**/*.test.js

After

node --test

9 of 9 legs green

Why the glob broke on Node 20, and the fix. The red squares count the failed legs on main. They don't place them in the matrix.

npm said no to a name that looked free

The tool started as tidyrepo. npm view tidyrepo returned a 404, so the name looked available.

Then npm publish failed with a 403. npm's similarity check strips punctuation, so tidyrepo collided with an existing tidy-repo. A 404 isn't proof a name is free: check the hyphenated and underscored versions too.

It's vibetidy now, which honestly fits better.

  1. npm view tidyrepo

    404

    Looks free

  2. npm publish

    403

    Refused

npm's similarity check strips punctuation

Already on npm

tidy-repo

tidyrepo

Mine

tidyrepo

tidyrepo

Same name after stripping

A 404 isn't proof. Check these too

  • tidyrepo
  • tidy-repo
  • tidy_repo

Nowvibetidy

The name check, step by step. A free name has to be free with its punctuation taken out.

Try it

It's MIT, zero runtime dependencies, and works with OpenAI, OpenRouter, Anthropic, Groq, DeepSeek, Together, or a local Ollama:

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. 1See the facts

    npx vibetidy readme --print-context

  2. 2Generate, review the diff, confirm

    npx vibetidy readme

  3. 3Nag on feature commits with no issue

    npx vibetidy issue-check --install-hook

Works with

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

MITlicense

0runtime dependencies

The three commands from the note, in the order you'd run them. The small drawings are a schematic.

If it writes something your repo doesn't back up, that's a bug. Open an issue on GitHub.

Plain text: vibetidy-grounded-readme.md · Atom feed · All notes

Contact

Say hello.

b.bousnguar@gmail.com

SAP Commerce Cloud, MuleSoft and Salesforce integration · Nantes · EN / FR