Skip to content

feat(go): render the usage line, from a third table that costs nothing unused - #964

Merged
jdx merged 4 commits into
mainfrom
go/help
Aug 17, 2026
Merged

feat(go): render the usage line, from a third table that costs nothing unused#964
jdx merged 4 commits into
mainfrom
go/help

Conversation

@jdx

@jdx jdx commented Aug 17, 2026

Copy link
Copy Markdown
Owner

The first half of help, and the half everything else hangs off.

argv.UsageLine([]string{"mise"}, mise.Root, mise.HelpText)
// mise [FLAGS] [TASK] <SUBCOMMAND>

Follows #959, which has merged; this now targets main directly.

All 211 of mise's usage lines match usage-lib byte for byte

That is the test worth having, rather than a snapshot of whatever this happens to produce. usage-lib builds the line from a spec through a template over a runtime model; this builds it from static tables. Reimplemented rules drift, so both are run over mise's real spec and compared — the same check benches/gate/tests/help.rs makes for usage-argv, against the same reference.

Two implementations checked against one oracle beats two checked against each other.

Why help text is a third table

Not more fields on Meta. Go's linker drops an unreferenced package-level symbol whole, so one table would make every CLI that applies a post-binding rule carry every help string in the spec — several hundred kilobytes of them for mise. Three tables let the linker enforce the split Rust gets from a feature flag:

a CLI that… carries mise-sized binary
only binds the parse tables 2.60 MB
applies the post-binding rules + Meta 2.82 MB
prints help + HelpText 2.82 MB

No init function in any of them. Demanded is precomputed into the help table for the same reason: required-and-undefaulted decides the brackets, and reaching into Meta to ask would drag it into every binary that prints help.

The fiddly parts

Ported rather than reinvented, with the parity test as the proof: the two-entry inline limit before collapsing to [FLAGS], the for a repeatable flag as distinct from a variadic value, [-- COMMAND]… with the brackets outside the separator, and naming a flag whose forms do not imply its name (env: -E… <ENV>).

What is still missing

-h and --help still need laying out — on the Rust side that is most of argv/src/help.rs. And errors: Error() returns unknown flag: --wat, which names the problem and helps nobody fix it.

🤖 Generated with Claude Code


Note

Low Risk
User-visible help strings only; no parser binding or auth changes, guarded by extensive parity tests.

Overview
Adds argv.UsageLine and a separate Help / HelpTable so the post-Usage: line is built from parse tables plus cold help metadata, without pulling Meta into binaries that only print help.

Rendering follows usage-lib rules: hide entries, inline up to two flags/args then collapse to [FLAGS] / [ARGS], required vs optional brackets (including flag values via ValueDemanded), repeatable flags, ---only positionals, and subcommand suffix.

Conformance adds help_test.go: all 211 mise command usage lines are compared byte-for-byte to usage-lib’s lowered usage strings (same oracle as benches/gate/tests/help.rs), plus focused cases for hidden entries and flag-value bracketing. runUsage is shared for lowering specs in tests.

Docs explain the three-table linker split (parse / Meta / HelpText) and what help work remains (-h pages, richer errors).

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

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jdx, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Limit details: You’ve used all 4 included reviews currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 20f19077-01ec-4fb9-be86-2e136b032b2a

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6f671 and 5be8b7f.

⛔ Files ignored due to path filters (5)
  • Cargo.lock is excluded by !**/*.lock
  • lib/src/go/snapshots/usage__go__tests__a_default_subcommand_points_into_the_tree.snap is excluded by !**/*.snap
  • lib/src/go/snapshots/usage__go__tests__a_whole_cli.snap is excluded by !**/*.snap
  • lib/src/go/snapshots/usage__go__tests__colliding_names_get_distinct_identifiers.snap is excluded by !**/*.snap
  • lib/src/go/snapshots/usage__go__tests__unknown_flags_are_inherited_and_overridable.snap is excluded by !**/*.snap
📒 Files selected for processing (10)
  • go/README.md
  • go/argv/help.go
  • go/conformance/conformance_test.go
  • go/conformance/help_test.go
  • go/internal/shadow/mise/tables.go
  • go/internal/spec/spec.go
  • lib/src/go/mod.rs
  • xtask/Cargo.toml
  • xtask/src/help_pages.rs
  • xtask/src/main.rs

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 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Go usage-line rendering backed by a separately linked help table and extends conformance coverage against usage-lib’s output.

  • Generates help metadata alongside parse and post-binding tables.
  • Renders flags, positional arguments, and subcommand placeholders with reference-compatible brackets and repeatability markers.
  • Adds mise-scale parity tests and focused coverage for nested flag-value requiredness and defaults.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the prior nested-value bracket concern is addressed by independently deriving and testing ValueDemanded against usage-lib’s reference output.

Important Files Changed

Filename Overview
go/argv/help.go Introduces usage-line rendering from parse and help tables, including independent flag-value bracket handling.
lib/src/go/mod.rs Generates the separate Go help table and now derives ValueDemanded from the nested value argument’s requiredness and default.
go/internal/spec/spec.go Extends the lowered Go model and builder to produce help metadata consistently with generated tables.
go/conformance/help_test.go Verifies mise-wide parity with usage-lib and directly covers all four relevant flag/value bracket combinations.
xtask/src/help_pages.rs Adds a deterministic maintainer utility for exporting usage-lib help pages as JSON.

Reviews (6): Last reviewed commit: "fix(go): stabilize dumped help width" | Re-trigger Greptile

Comment thread lib/src/go/mod.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d2ed9bd. Configure here.

Comment thread xtask/src/help_pages.rs
Base automatically changed from go/generate-meta to main August 17, 2026 12:20
@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

The comparison never ran — an earlier step failed.

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 64541 5895248 91x
usage: argv -> struct                            1085 ns      1.08 µs
clap: build tree + parse -> struct             506133 ns    506.13 µs
clap: parse -> struct, tree reused              23989 ns     23.99 µs
clap: build tree only                          312979 ns    312.98 µs

d2ed9bde9494 vs `` · measured on the runner, not pushed to the history.

jdx and others added 3 commits August 17, 2026 12:22
…g unused

The first half of help, and the half everything else hangs off: `argv.UsageLine`
renders what a page prints after `Usage: `.

    argv.UsageLine([]string{"mise"}, mise.Root, mise.HelpText)
    // mise [FLAGS] [TASK] <SUBCOMMAND>

**All 211 of mise's usage lines match usage-lib's byte for byte.** That is the
test worth having rather than a snapshot of what this happens to produce:
usage-lib builds the line from a spec through a template over a runtime model,
this builds it from static tables, and reimplemented rules drift. Both are run
over mise's real spec and compared — the same check `benches/gate/tests/help.rs`
makes for usage-argv, against the same reference. Two implementations checked
against one oracle beats two checked against each other.

Help text is a *third* table rather than more fields on `Meta`, and the reason is
the linker: Go drops an unreferenced package-level symbol whole, so one table
would make every CLI that applies a post-binding rule carry every help string in
the spec — several hundred kilobytes of them, for mise. Three tables let the
linker enforce the split that Rust gets from a feature flag, and measured at
mise's size it does: 2.60 MB binding only, 2.82 MB once something references
either cold table, no init function in any of them.

`Demanded` is precomputed into the help table rather than read from `Meta` for
the same reason — required-and-undefaulted is what decides whether the line
angles an entry or brackets it, and reaching into the post-binding table to ask
would drag it into every binary that prints help.

The fiddly parts are ported rather than reinvented, and the parity test is what
proves it: the two-entry inline limit before collapsing to `[FLAGS]`, the `…` for
a repeatable flag as distinct from a variadic value, `[-- COMMAND]…` with the
brackets outside the separator, and naming a flag whose forms do not imply its
name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… does

A review comment about `Demanded` turned out to be wrong about the reference and
right that something was off next door.

The claim was that a flag whose default sits on its nested value should not be
angled. usage-lib angles it: `<--jobs [n]>`, and accepts the invocation without
it, printing `jobs=4`. So `Demanded: true` is correct, and the tables are not
inconsistent — they answer different questions, and usage-lib gives the same two
answers.

What the case did expose is the *value's* brackets. usage-lib applies the same
required-and-undefaulted test to the value that it applies to the flag, and the
two are independent:

    flag "--tool <TOOL>"                          → [--tool <TOOL>]
    flag "--v <n>" required=#true                 → <--v <n>>
    flag "--opt [n]"                              → [--opt [n]]
    flag "--jobs <n>" required { arg default="4" } → <--jobs [n]>

This angled the value unconditionally, so it was wrong on three of those four.
The 211-command parity test did not catch it because every flag value mise
declares is required and undefaulted — a fixture cannot find what it does not
contain, which is the second time that has been true in this stack.

Worth recording that usage-argv angles the value unconditionally too, so it
differs from usage-lib on the same three. This follows usage-lib, which is the
reference the help output is measured against and the oracle this suite already
uses. That makes the Rust side's behaviour a finding rather than something to
copy.

The four combinations are now a test, each asserted against usage-lib's own line
as well as against the expected string — so the expectation cannot quietly become
a record of what this code does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…them

usage-go renders help from static tables where usage-lib renders it from a spec
through a template, and reimplemented rules drift. `benches/gate/tests/help.rs`
holds usage-argv to the standard of matching all 211 of mise's pages byte for
byte; a Go suite needs the same reference, and cannot call
`usage::docs::cli::render_help` because it is not Rust.

Shelling out to the CLI almost works: a usage-shebang script prints a page, and
mise's spec can be embedded in one. But a subcommand's page arrives wrapped in
the frame miette draws around an error, and a reference that *might* have been
reflowed on the way out is worse than no reference at all — the test would fail
and the obvious response would be to change the implementation until it matched
text nobody had rendered.

So `xtask help-pages <spec.kdl>` dumps every command's short and long page as
JSON, unwrapped, in one pass. One invocation rather than 211, and no frame to
strip.

In xtask rather than the CLI deliberately: this is a maintainer's tool for
checking one implementation against another, not an output format anybody asked
for. `usage generate` stays the list of things a spec author wants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdx
jdx merged commit f6ed4c9 into main Aug 17, 2026
9 of 10 checks passed
@jdx
jdx deleted the go/help branch August 17, 2026 12:41
jdx added a commit that referenced this pull request Aug 17, 2026
…of mise's (#974)

The whole page, not just the usage line: header, `Commands`,
`Arguments`, `Flags` and `Global flags`, columns lined up, inherited
globals resolved the way the parser resolves them.

**All 211 of mise's pages match usage-lib byte for byte.**

```
$ mise run test:go
    211 short pages match usage-lib exactly
    211 usage lines match usage-lib exactly
```

That is the standard
[`benches/gate/tests/help.rs`](https://github.com/jdx/usage/blob/main/benches/gate/tests/help.rs)
holds usage-argv to, and the only one worth having: help text is the
part of a CLI a user actually reads, and a rule reimplemented from a
spec-driven template into static tables drifts unless something checks
every page. The reference is `xtask help-pages`, which landed in #964.

## 167 matched first run. The four that did not

Each reads as arbitrary until the diff shows it:

- **The short page prints an entry's whole `help`, not its first line.**
mise has flags whose help is two lines, and the second appears on the
page unindented.
- **`(default: …)` is printed for an argument and not for a flag.** Not
an oversight in usage-lib — a distinction the long page picks back up.
- **The short-flag column is four wide, and only a _bare_ short goes in
it.** A flag carrying a declared name its forms do not imply (`jobs: -j
--parallel`) is left whole rather than split around a comma.
- **A page offers a spelling only where the flag it describes is the one
that would bind it.** The fiddliest part and the most load-bearing.

## That last rule, since it is the one to review

A nearer command claiming `--jobs` leaves an inherited `--workers`
findable. Hidden flags still reserve their spellings, because the parser
still binds them. A long anywhere in scope beats a negation, because
`longFlag` asks for every long form before it asks for any negation. And
`--help`/`--version` are offered only under the spellings nothing else
has claimed.

Advertising a flag that something else binds is the lie the model exists
to prevent, and none of it is visible without the parity test.

## Both halves are tested

`go/conformance` proves the renderer against usage-lib using tables
built from a lowered spec at run time. That leaves the emitter
unchecked, and the help table is where a dropped field is least visible
— a missing alias or annotation changes one line of one page. So
`internal/shadow/mise` renders the whole tree from what the generator
actually wrote, plus one page asserted in full.

## What is left

`--help`. It wraps long descriptions and switches to a two-line layout
for entries with a longer form; `ShortHelp` does neither.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Help rendering and tests only; no parser binding or security-sensitive
paths changed. Risk is mainly user-visible help text regressions,
guarded by byte-for-byte reference tests.
> 
> **Overview**
> Adds **`argv.ShortHelp`** to render full short help (`-h`): header,
Commands, Arguments, Flags, Global flags, examples, and aligned columns.
**`Help`** grows page-only fields (aliases, choices, env, defaults,
before/after help, examples), and **`flagUsageShown`** / **`scope.go`**
limit listed flag spellings to what the parser would bind, including
inherited globals, shadowing, negations, and parser-supplied `--help` /
`--version`.
> 
> Conformance now byte-matches **all 211** mise short pages against
`xtask help-pages` (`page_test.go`), compares Rust **`usage generate
go`** vs runtime lowering (`producers_test.go`), and smoke-renders
generated shadow tables (`meta_test.go`). README documents short help
parity and notes **`--help`** long-page layout as still missing.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8b8026a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added complete short-help page rendering for commands.
* Help pages now include aliases, examples, choices, environment
variables, defaults, and custom surrounding text.
* Improved display of local and inherited global flags, including
supported spellings and negations.
  * Hidden and shadowed options are omitted from help output.

* **Documentation**
* Updated documentation to confirm short-help compatibility across all
211 reference pages.

* **Tests**
* Added conformance coverage verifying short-help pages match the
reference output byte-for-byte.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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