Skip to content

feat(argv): answer --help and -h - #870

Merged
jdx merged 2 commits into
agent/help-long-parityfrom
agent/help-wiring
Aug 14, 2026
Merged

feat(argv): answer --help and -h#870
jdx merged 2 commits into
agent/help-long-parityfrom
agent/help-wiring

Conversation

@jdx

@jdx jdx commented Aug 13, 2026

Copy link
Copy Markdown
Owner

The parser recognises both itself rather than a CLI declaring them, which is the only
arrangement that does not change what help says: no spec declares --help, so a table entry
for one would put it in the rendered page and in the emitted KDL, and the page would then
disagree with the spec it came from.

Recognised after the command's own flags, so a CLI that declares its own --help or -h
keeps it and binds it like any other flag.

A request comes back as Error::Help, carrying the command it was asked about — mise config --help is a question about config, and the parser is what knows how far the words reached.
Not printed: a library that writes to stdout on its own is one an adopter cannot embed, and a
parse that stops for help has produced no value, which is the shape every caller already
handles. clap does the same thing for the same reason. parse(), the convenience that reads the
process's own arguments, is the one place that prints and exits.

-h renders the short form and --help the long one, as clap has them.

The first attempt put the two flags in every command's parse table, which flatten then spliced
twice — a command that flattened another had two -h entries and tripped the duplicate-key
check. Recognising them in the parser instead removes the table entries, the metadata mismatch
and the collision at once.

Costs 111 instructions of 29,961, and only on a flag that matched nothing.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com


Stack created with GitHub Stacks CLIGive Feedback 💬


Note

Low Risk
User-facing behavior change is limited to new help handling on the parse path; embedders must handle Error::Help if they use parse_from instead of parse().

Overview
Built-in help flags are recognized in the parser (not in per-command tables), so help output and emitted KDL stay aligned with specs that never declare --help. --help and -h are resolved only after a command’s own flags, so CLIs that define their own help/h still bind them normally.

Parsing stops with Error::Help { cmd, long }, targeting the command in scope (e.g. ex ls --helpls). usage_argv::help::find / render map that Command to metadata and short vs long help text. parse_from returns the error for embedders; parse() renders to stdout and exits 0.

usage-derive intercepts help flag events before apply and special-cases Error::Help in parse(). PLAN.md marks help rendering as wired; the dedicated help subcommand remains open. Conformance tests cover long/short pages, scoped help, spec omission, custom help flags, help before validation, and wrapper forwarding after -- / automatic double-dash.

Reviewed by Cursor Bugbot for commit 12f2321. Bugbot is set up for automated code reviews on this repo. Configure here.

Where help is recognised, and why it matters

In the parser, not in any command's table. That is the only arrangement that does not change
what help says: no spec declares --help, so a table entry for one would put it in the
rendered page and in the emitted KDL, and the page would then disagree with the spec it came
from. The 211-page parity is unchanged by this PR, which is the check on that claim.

Recognised after a command's own flags, so a CLI that declares its own --help or -h keeps
it and binds it like any other flag.

How it comes back

Error::Help { cmd, long } — carrying the command it was asked about, because mise config --help is a question about config and the parser is what knows how far the words reached.

Not printed. A library that writes to stdout on its own is one an adopter cannot embed, and a
parse that stops for help has produced no value — the shape every caller already handles. clap
does the same thing for the same reason. parse(), the convenience that reads the process's own
arguments, is the one place that prints and exits.

-h renders the short form, --help the long one, as clap has them.

The design that did not work

The first attempt put both flags in every command's parse table. flatten then spliced them
twice — a command that flattens another had two -h entries and tripped the duplicate-key check
— and the metadata had to gain an exemption from "every flag in the table needs metadata" to keep
them out of the page. Moving the knowledge into the parser removed the table entries, the
metadata mismatch, and the collision together.

Cost

111 instructions of 29,961, and only on a flag that matched nothing — the check sits after the
lookup that failed. 850 ns against clap's 435 µs.

Verification

Six tests: the two forms differ and ask for different pages; help is about the command the words
reached rather than the root; the flags appear in neither the page nor the emitted spec; a CLI
declaring its own keeps it; help answers even when a required argument is missing (which is when
someone needs it most); and the fixture still parses normally when nobody asks.

Three mutation checks — dropping the long form, dropping the short one, and checking help
before a command's own flags, which is what would silently steal a declared --help.

Next

The help subcommand — mise help config ls — which every CLI with subcommands should have, and
which the rendered page already advertises.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 3df4f34b-63a2-455a-98c6-511af71ac783

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds parser-level handling for -h and --help, returning command-scoped help requests without adding synthetic flags to generated specifications.

  • Adds short- and long-help recognition after declared flag lookup.
  • Adds metadata lookup and short/long help rendering for the requested command.
  • Updates generated parse_from and parse behavior to return or print help as appropriate.
  • Adds conformance and parser tests for help precedence, rendering, and forwarding after separators.

Confidence Score: 4/5

The PR is not yet safe to merge because leading help tokens still bypass forwarding for commands configured with UnknownFlags::Value.

The parser returns synthetic help events before reaching the unknown-flag positional fallback, so forwarding wrappers consume a leading --help or -h instead of passing it to the wrapped command.

Files Needing Attention: argv/src/lib.rs

Important Files Changed

Filename Overview
argv/src/lib.rs Adds reserved help flags, parser recognition, and tests covering declared overrides and separator-based forwarding.
argv/src/help.rs Adds identity-based command metadata lookup and command-scoped short/long help rendering.
derive/src/codegen.rs Converts synthetic help events into Error::Help and renders them at the process-level parse() boundary.
conformance/tests/help_request.rs Exercises help forms, command scope, metadata omission, declared overrides, and precedence over post-binding errors.
PLAN.md Updates the implementation plan to document parser-level help handling and the remaining help-subcommand work.

Reviews (6): Last reviewed commit: "test(argv): pin that a wrapper still for..." | Re-trigger Greptile

@jdx
jdx force-pushed the agent/help-wiring branch from f6fbc8e to 12b3ab6 Compare August 13, 2026 23:12
Comment thread argv/src/lib.rs
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
markdown ▁████████ 175,762,001 → 175,941,539 +0.10% 16.93 → 16.19ms -4.35%
startup ▁████████ 1,222,055 → 1,222,185 +0.01% 1.05 → 0.96ms -9.15%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

Shadow comparison

Parsing mise use -g node@20 against a shadow of mise's committed spec.
Reported, not gated: the shadow grows as the derive learns to express more, so
what to watch is the ratio rather than either column.

usage clap ratio
instructions, cold parse 29943 5879475 196x
usage: argv -> struct                             862 ns      0.86 µs
clap: build tree + parse -> struct             491862 ns    491.86 µs
clap: parse -> struct, tree reused              23885 ns     23.89 µs
clap: build tree only                          314530 ns    314.53 µs

12f232106aa9 vs fd7a4f8e5623 · measured on the runner, not pushed to the history.

jdx commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

The mechanism is right, the behavior is intended, and it is now pinned by a test (df327ba).

A wrapper has two ways to say "hand this on", and help interception sits before both rather than around them:

  • after a typed --, flag interpretation has stopped, so --help and -h bind as values;
  • after a double_dash = automatic argument takes its first value, the same is true without the caller typing a separator — which is the mode a forwarding command should be declaring.

Before either takes effect, --help is a question about the wrapper itself: mise run --help asks about run, and that is also what clap does. The test asserts all three.

UnknownFlags::Value on its own is not a forwarding declaration — it is the default for every command — so it cannot be the signal that suppresses help without taking --help away from most of the CLI.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.

jdx commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

Note on the force-push: this branch and #872 had been rebuilt on a parallel copy of #866's commits rather than on #866 itself, so as pushed they were missing e02c902 and the spec-fallback fix — merging them would have reverted work from the two PRs below. Both are now rebased onto #866's actual head, and the stack is one linear chain again. No content of this PR changed beyond the added test.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.

@jdx
jdx force-pushed the agent/help-wiring branch from df327ba to 8ab3d5a Compare August 14, 2026 00:22
@jdx
jdx force-pushed the agent/help-wiring branch from 8ab3d5a to ec4335d Compare August 14, 2026 01:25
@jdx
jdx force-pushed the agent/help-wiring branch from ec4335d to 8ab3d5a Compare August 14, 2026 02:04
jdx and others added 2 commits August 14, 2026 02:30
The parser recognises both itself rather than a CLI declaring them, which is the only
arrangement that does not change what help *says*: no spec declares `--help`, so a table entry
for one would put it in the rendered page and in the emitted KDL, and the page would then
disagree with the spec it came from.

Recognised *after* the command's own flags, so a CLI that declares its own `--help` or `-h`
keeps it and binds it like any other flag.

A request comes back as `Error::Help`, carrying the command it was asked about — `mise config
--help` is a question about `config`, and the parser is what knows how far the words reached.
Not printed: a library that writes to stdout on its own is one an adopter cannot embed, and a
parse that stops for help has produced no value, which is the shape every caller already
handles. clap does the same thing for the same reason. `parse()`, the convenience that reads the
process's own arguments, is the one place that prints and exits.

`-h` renders the short form and `--help` the long one, as clap has them.

The first attempt put the two flags in every command's parse table, which flatten then spliced
twice — a command that flattened another had two `-h` entries and tripped the duplicate-key
check. Recognising them in the parser instead removes the table entries, the metadata mismatch
and the collision at once.

Costs 111 instructions of 29,961, and only on a flag that matched nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Supplying `--help` narrows what a forwarding command can be handed, so the two
mechanisms that make forwarding possible are worth stating: after a typed `--`,
and after an `automatic` argument takes its first value, flag interpretation has
stopped and `--help` is a value like any other.

Before either takes effect, `--help` is still a question about the wrapper — `mise
run --help` asks about `run` — which is the line between the two cases.

Raised by greptile on the mechanism; the tests are the answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdx
jdx force-pushed the agent/help-wiring branch from 8ab3d5a to 12f2321 Compare August 14, 2026 02:30
@jdx
jdx merged commit c694d20 into main Aug 14, 2026
9 of 13 checks passed
@jdx
jdx deleted the agent/help-wiring branch August 14, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant