feat(argv): render the usage line, byte-identical to usage-lib's - #854
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Greptile SummaryThe PR adds a feature-gated usage-line renderer backed by static command metadata and aligns derived flag names with their command-line forms.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (7): Last reviewed commit: "fix(derive): take a flag's placeholder f..." | Re-trigger Greptile |
Instruction counts
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 comparisonParsing
|
|
Real, and it goes to the heart of what this PR claims. usage-lib clears Fixed where the line is rendered rather than by clearing the flag in the metadata: the metadata is what the emitted spec is built from, and Worth explaining why the 211-line parity test sails past this. Every defaulted positional in mise's spec is written Both call sites mutation-checked. The collapse threshold needed a case of its own: AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
ecc6bb6 to
6586ea7
Compare
|
Right — I fixed arguments and left flags reading Both the brackets and the collapse threshold now use the same rule, and the fixture grew a defaulted flag plus three more so the collapsed AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
`Usage: mise use [OPTIONS] <TOOL@VERSION>…` — the line at the top of `--help`, and the first thing a CLI framework has to be able to produce. Built from the same `&'static` metadata a parse ignores, so a binary that never asks for help pays nothing for being able to. usage-lib renders this through a tera template over a runtime model. This crate has no `Spec` at run time, only tables, so the rules are reimplemented — and reimplemented rules drift. The test runs both over mise's real spec and compares all 211 lines, because an adopter's help text changing is a visible regression even when the change is one bracket. The shadow is generated from the same KDL usage-lib is handed, so the two describe the same CLI by construction rather than by a fixture kept in step by hand. Two things it caught: An argument that only takes what follows a `--` is bracketed *around* the separator — `[-- COMMAND]…`, one optional thing — where I had written `-- [COMMAND]…`, a literal `--` followed by an optional word. Five of mise's commands read that way. A flag is named after the form it answers to, not after the Rust field holding it. usage-lib derives the name from the first long form, and the derive was kebab-casing the field ident — so `type_` gave a flag called `type-`, which help printed as `type-: -t --type` and errors reported as `type-`. An explicit `name` still wins, and a flag with no long form keeps its short, as usage-lib does. Selectors resolve by form rather than by name, so nothing that refers to a flag changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rs it The usage line took optionality from `required` alone. usage-lib clears `required` while *parsing* a spec that declares a default — a defaulted argument is one the user may leave out — and then renders from `required`, so it writes `[dir]` where this wrote `<dir>`, for an argument the parser is perfectly happy to omit. The same signal decides whether a collapsed list reads `<ARGS>…` or `[ARGS]…`, so a command of entirely defaulted arguments was marked required there too. Applied where the line is rendered rather than by clearing the flag in the metadata: the metadata is what the emitted spec is built from, and `required` there means what the author wrote. Worth saying why the 211-line parity test did not catch it. Every defaulted positional in mise's spec is written `required=#false default=…`, so there is nothing to normalize and both sides agree. A *derived* argument is required by its type and defaulted by its attribute, which is the shape that diverges — so the new test compares the two renderers over one derived declaration, and also parses it to show the brackets are telling the truth. Both call sites mutation-checked; the collapse threshold needed a case of its own, since three defaulted arguments are what it takes to reach it.
|
Real, and worth doing — but the code it names is not in this PR's own commits, and I have deliberately not touched it. The block that sets The name is right and should stay: usage-lib derives it the same way, so Happy to apply it here if that branch goes idle, or it can be picked up by whoever owns it. AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
…'s about Two findings from Cursor Bugbot, both regressions from this stack. A flag is named after the form it answers to, and for a short-only flag that form is one character — right for the flag's name and useless as the name of its *value*, since help and the KDL both fall back to `flag.name` for the placeholder. `-j <j>` where the field is called `jobs`. The descriptive name is kept as the value name before the flag's own is overwritten. And when a program's two descriptions are independent, the generator skipped the root's doc comment and wrote them into the *usage* attribute list only — which the clap dialect never emits, so the clap shadow stopped describing the program at all. clap takes an independent `about` and `long_about`, and a fixture for comparing two frameworks cannot have one of them missing the CLI's own description. Both mutation-checked. `-h` parity is unchanged at 211 of 211, which is what says the value-name fix did not move the rendering. Two older findings on #854 were already answered by the `demanded`/`flag_demanded` helpers: a defaulted argument or flag is one the parser fills, so the usage line brackets it as optional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A flag is named after the form it answers to, which for a short-only flag is one character. That is right for the flag's name and useless as the name of its *value*: help and the KDL both fall back to `flag.name` for the placeholder, so `-j <j>` where the field is called `jobs`. The descriptive name is kept as the value name before the flag's own is overwritten. Fixed here rather than further up the stack, which is where I had put it — the finding is against this PR's change, so it belongs in this PR. A fix that only exists in a descendant leaves the branch it was reported on still carrying the bug, and this one merges on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s about Two findings from Cursor Bugbot, both regressions from this stack. A flag is named after the form it answers to, and for a short-only flag that form is one character — right for the flag's name and useless as the name of its *value*, since help and the KDL both fall back to `flag.name` for the placeholder. `-j <j>` where the field is called `jobs`. The descriptive name is kept as the value name before the flag's own is overwritten. And when a program's two descriptions are independent, the generator skipped the root's doc comment and wrote them into the *usage* attribute list only — which the clap dialect never emits, so the clap shadow stopped describing the program at all. clap takes an independent `about` and `long_about`, and a fixture for comparing two frameworks cannot have one of them missing the CLI's own description. Both mutation-checked. `-h` parity is unchanged at 211 of 211, which is what says the value-name fix did not move the rendering. Two older findings on #854 were already answered by the `demanded`/`flag_demanded` helpers: a defaulted argument or flag is one the parser fills, so the usage line brackets it as optional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c2d4575. Configure here.
The value name falls back to the flag's name, and a flag is named after the form it answers to — so keeping the field's kebab-cased ident as the placeholder was only right for a flag that has no long form. With one, the long form *is* the descriptive name, and a field called `type_` rendered `--type <type->`. Narrowed to the case it was for: a short-only flag, where the name becomes one character and `-j <j>` says nothing. Found by Cursor Bugbot on #854. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s about Two findings from Cursor Bugbot, both regressions from this stack. A flag is named after the form it answers to, and for a short-only flag that form is one character — right for the flag's name and useless as the name of its *value*, since help and the KDL both fall back to `flag.name` for the placeholder. `-j <j>` where the field is called `jobs`. The descriptive name is kept as the value name before the flag's own is overwritten. And when a program's two descriptions are independent, the generator skipped the root's doc comment and wrote them into the *usage* attribute list only — which the clap dialect never emits, so the clap shadow stopped describing the program at all. clap takes an independent `about` and `long_about`, and a fixture for comparing two frameworks cannot have one of them missing the CLI's own description. Both mutation-checked. `-h` parity is unchanged at 211 of 211, which is what says the value-name fix did not move the rendering. Two older findings on #854 were already answered by the `demanded`/`flag_demanded` helpers: a defaulted argument or flag is one the parser fills, so the usage line brackets it as optional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Right, and the fix belongs here rather than in a descendant — copying the field ident into the value name ran for every unnamed value flag, so a field called `type_` with `long = "type"` rendered `--type `. Narrowed to the case it was for: a short-only flag, where the name becomes one character and `-j ` says nothing. A long form is already the descriptive name. AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |

Usage: mise use [OPTIONS] <TOOL@VERSION>…— the line at the top of--help, and the firstthing a CLI framework has to be able to produce. Built from the same
&'staticmetadata aparse ignores, so a binary that never asks for help pays nothing for being able to.
usage-lib renders this through a tera template over a runtime model. This crate has no
Specat run time, only tables, so the rules are reimplemented — and reimplemented rules drift. The
test runs both over mise's real spec and compares all 211 lines, because an adopter's help
text changing is a visible regression even when the change is one bracket. The shadow is
generated from the same KDL usage-lib is handed, so the two describe the same CLI by
construction rather than by a fixture kept in step by hand.
Two things it caught:
An argument that only takes what follows a
--is bracketed around the separator —[-- COMMAND]…, one optional thing — where I had written-- [COMMAND]…, a literal--followed by an optional word. Five of mise's commands read that way.
A flag is named after the form it answers to, not after the Rust field holding it. usage-lib
derives the name from the first long form, and the derive was kebab-casing the field ident —
so
type_gave a flag calledtype-, which help printed astype-: -t --typeand errorsreported as
type-. An explicitnamestill wins, and a flag with no long form keeps itsshort, as usage-lib does. Selectors resolve by form rather than by name, so nothing that
refers to a flag changes.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Stack created with GitHub Stacks CLI • Give Feedback 💬
Note
Low Risk
Cold-path help formatting behind
spec, with broad parity tests; derive naming changes align emitted KDL/help with usage-lib and do not alter parse-time selector matching.Overview
Adds
usage_argv::help::usage_linebehind the existingspecfeature so the top-of-help invocation summary is built from staticCommandMetatables without a runtime spec model.Parity: The gate bench compares every mise command’s line (211) against usage-lib from the same KDL; PLAN.md marks the usage line as done.
Rendering fixes: Defaulted flags/args render as optional (
required && default.is_empty()), not rawrequired.--positionals use[-- NAME]…with brackets outside the separator. Flags/args collapse to[FLAGS]/[ARGS]…past an inline limit of 2.Derive: Flags without an explicit
nameare named from the first long or short form (fixestype_→type-); short-only value flags keep the field ident asvalue_name.Tests: Gate
help.rs, conformancederive/metadatafor defaulted and renamed flags.Reviewed by Cursor Bugbot for commit 32ff41b. Bugbot is set up for automated code reviews on this repo. Configure here.
The standard
All 211 of mise's commands render byte-identically to usage-lib.
usage-lib builds this through a tera template over a runtime model. usage-argv has no
Specatrun time — only
&'statictables — so the rules are reimplemented here, and reimplemented rulesdrift. The test runs both over mise's real spec and compares every line, because an adopter's
help text changing is a visible regression even when the change is one bracket. The shadow is
generated from the same KDL usage-lib is handed, so the two describe the same CLI by
construction rather than by a fixture kept in step by hand.
The parity test is also what found the four metadata gaps: three in #853 below this, and one
here.
What it caught here
A
--argument is bracketed around the separator. usage-lib writes[-- COMMAND]…— oneoptional thing — where I had
-- [COMMAND]…, a literal--followed by an optional word. Fiveof mise's commands read that way.
A flag is named after the form it answers to, not the field holding it. usage-lib derives
the name from the first long form; the derive was kebab-casing the Rust ident. So
type_gave aflag called
type-, printed by help astype-: -t --typeand reported by errors astype-.An explicit
namestill wins, and a flag with no long keeps its short, as usage-lib does.Selectors resolve by form rather than by name, so nothing that refers to a flag changes — and
error messages get better for free.
Cost
None on the parse path: this reads the cold metadata a successful parse never touches, and lives
behind the existing
specfeature. A binary that never renders help does not carry theformatting code.
Not yet
The line only. The sections below it — about, arguments, flags grouped by heading, subcommands,
examples — and the
--help/-hwiring in the derive are the next two PRs. The line comes firstbecause everything else is arranged around it, and because it is the piece with an exact oracle.
AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.