feat(cli): --help that never starts a run, and unknown flags are refused - #34
Merged
Merged
Conversation
`run --help` used to run the entire suite. bin/twd-cli.js dispatched on argv[2] alone, parseRunArgs dropped the token it did not know, and the run proceeded: minutes of wall clock and a browser nobody asked for, with nothing printed to say the flag was not understood. An agent hit exactly this and ended up reading the bin from node_modules to find --changed-since. Help is now decided in the bin before either parser runs and before the dynamic import of src/index.js, so it loads no puppeteer, reads no config and touches no git. `--help`/`-h` win wherever they appear after the command, and a bare `help` (optionally `help <command>`) does the same. The one usage block is split per command in src/usage.js. `run --help` now lists --update-snapshots and --ci, which appeared in neither the old block nor the README, and both value forms are stated once in the shared header. RUN_FLAGS / MERGE_FLAGS are exported from the parser so the usage test derives the expected set from the code rather than a copy of it. An unknown command moves to stderr with exit 1; it used to print usage on stdout with exit 1, which a script cannot tell from a run's output. tests/cli.test.js spawns the real bin. Every case returns before the dynamic import, so no browser is involved, and a regression to the old behaviour fails on exit code alone since there is no dev server.
parseRunArgs and parseMergeArgs ignored any --token no branch claimed. That is what let `run --help` run the suite, and it is still what a typo does today: `--tests foo` ran every test with a filter the caller believed they had set, and nothing said otherwise. Both parsers now collect the `--`-prefixed strays and throw before anything runs: twd-cli run: unknown option --tests Did you mean --test? Run `twd-cli run --help` to see every option. The bin already prints a parser error to stderr and exits 1, so no routing changed. The suggestion is a prefix match first (--output → --out, --test-filter → --test) and otherwise the nearest known flag within two edits, which is tight on purpose: --out passed to run is a real mistake and must not come back as "Did you mean --ci?". This is what makes --help reliable rather than cosmetic. A future flag the bin forgets to route fails here instead of running the suite. Positionals keep their treatment: merge takes the first one as <dir>, run ignores them, and only `--` strays are refused.
README gets a short Getting help section next to Basic Usage: the three help invocations and what a rejected typo looks like. CLAUDE.md records where help is resolved and why that position matters, what RUN_FLAGS / MERGE_FLAGS are for, the suggestion budget, and that tests/cli.test.js is the one file allowed to spawn a real process.
TWD Contract Validation
23 passed · 41 failed · 3 warnings · 1 skipped Failed validations./contracts/users-3.0.json
./contracts/posts-3.1.json
./contracts/products-3.0.json
./contracts/events-3.1.json
|
Merged
kevinccbsg
added a commit
that referenced
this pull request
Sep 24, 2026
--help that never starts a run, per-command usage, and the refusal of unknown flags (#34). The record action's cli-version default moves to 1.9.0 in the same commit. Pinning the action to a SHA only fixes how the CLI is invoked, so leaving the default on 1.8.1 would ship an action that still runs the CLI without any of this. package-lock.json carries the two version fields and nothing else: no install was run, so the wasm32-wasi optional packages and their @emnapi deps are untouched and npm ci on Linux is unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What you get
Today
run --helpruns the entire suite. The bin dispatches onargv[2]alone andparseRunArgsdrops any token it does not know, so the run proceeds with nothing said. An agent hit exactly this, waited out the whole suite, and ended up readingbin/twd-cli.jsfromnode_modulesto find--changed-since.How it is built
Three layers, each one there to make the one above it trustworthy:
src/index.jsis imported dynamically somergenever loads puppeteer; the help path returns at that same point, so it reads no config, touches no git and launches no browser.--help/-hwin wherever they appear after the command.src/usage.js).run --helpnow lists--update-snapshotsand--ci, which appeared in neither the old block nor the README.RUN_FLAGS/MERGE_FLAGSare exported from the parser and the usage test derives the expected help from them, so a flag added without a help line fails the suite.--flagsthrow instead of being dropped. This is what makes--helpreliable rather than cosmetic: a future flag the bin forgets to route fails loudly instead of running the suite. The suggestion is a prefix match, else the nearest known flag within two edits. Tight on purpose:--outpassed torunmust not come back as "Did you mean --ci?".Behaviour changes to glance at
twd-cli bogusprints usage on stderr now (was stdout) and still exits 1.--flagneither parser knows used to be ignored; it now fails the command before anything runs. Both composite actions only pass known flags, and I found no other caller in the workspace passing an unknown one.tests/cli.test.jsspawns the real bin. Every case returns before the dynamic import, so no browser is involved, and a regression to the old behaviour fails on exit code alone since there is no dev server.