feat(cli): declare what each usage command does to the world - #751
Conversation
usage added `effect=` and shipped it to mise, hk, pitchfork, aube and
communique, and then declared nothing for its own 16 commands. Anything
pointed at usage's spec — its docs, `usage mcp` — sees empty fields on
the tool that invented the feature.
complete-word read
generate read
generate completion read
generate fig read --out-file write
generate manpage read --out-file write
generate markdown read --out-dir write
--out-file write
generate sdk write
lint read
sponsors read
Most of these print to stdout and take an optional flag to write to a
file instead, so they sit at `read` with the flag raising them. That is
the composition rule earning its place rather than being asserted in a
doc: `usage g markdown -f x.kdl` only reads, and the same command with
`--out-file` writes.
`generate sdk` is the exception. Its `-o --output` is required, so there
is no read-only way to invoke it and the effect belongs on the command.
The five shell commands — `bash`, `fish`, `zsh`, `powershell`, `exec` —
are deliberately unset, because they run a script the user supplied and
their effect is whatever that script does. `read` in particular would be
dangerous. A test asserts every unclassified command has an entry in
`UNCLASSIFIED` with its reason, so nothing goes unlabeled by accident.
Applied to the derived spec via `clap_usage::spec` (#743) since clap
cannot express it, the same shape as mise's `command_effects`. Also
emits `min_usage_version "4.0"`, because an older `usage` rejects a spec
carrying `effect=` with "unsupported cmd prop effect".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe CLI now classifies command and output-flag effects, applies them during usage-spec generation, emits usage version metadata, and synchronizes the resulting effect annotations across usage specifications and CLI reference documentation. ChangesCLI effect metadata
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CliMetadata
participant UsageSpec
participant CommandEffects
CliMetadata->>UsageSpec: build spec from CLI metadata
UsageSpec->>CommandEffects: apply command and flag effects
CommandEffects-->>UsageSpec: classified spec
UsageSpec-->>CliMetadata: emit usage version and spec
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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 SummaryAdds effect metadata to the
Confidence Score: 5/5The PR appears safe to merge with no concrete correctness or security issues identified. The declared effects match the reachable command implementations and output paths, the generated specification remains structurally valid, and the checked-in documentation consistently reflects the new metadata. Important Files Changed
Reviews (1): Last reviewed commit: "feat(cli): declare what each usage comma..." | Re-trigger Greptile |
Instruction counts
1 benchmark(s) above the 1% gate: 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.
|
|
The
Same binary, more input, more output. And no code on the So the gate fired on the corpus growing, which means it will fire on any PR that adds a command or a flag — for doing its job. That's a property of pointing the bench at a file that changes with the CLI, not of this change. Two ways forward, and I'd rather you picked than have me quietly widen your gate:
This comment was generated by an AI coding assistant. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cli/src/command_effects.rs (1)
153-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert declared unclassified commands remain unclassified.
This only checks
actual_unclassified - UNCLASSIFIED. If a user-script command is added toEFFECTS, it disappears fromaccidentaland the test still passes. Compare the actual unset-path set withUNCLASSIFIEDin both directions.Proposed fix
- let deliberate: HashSet<_> = UNCLASSIFIED.iter().map(|(path, _)| *path).collect(); - let accidental: Vec<_> = commands() + let deliberate: HashSet<_> = UNCLASSIFIED + .iter() + .map(|(path, _)| (*path).to_owned()) + .collect(); + let actual: HashSet<_> = commands() .into_iter() - .filter(|(path, classified)| !classified && !deliberate.contains(path.as_str())) + .filter(|(_, classified)| !classified) .map(|(path, _)| path) .collect(); - assert!( - accidental.is_empty(), - "unclassified with no entry in UNCLASSIFIED: {accidental:?}" - ); + assert_eq!(actual, deliberate, "unclassified commands must match UNCLASSIFIED");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cli/src/command_effects.rs` around lines 153 - 165, The nothing_is_unclassified_by_accident test must compare the complete set of commands with unset effects against UNCLASSIFIED in both directions. Build the actual unclassified path set from commands(), assert it equals the declared UNCLASSIFIED set, and retain the existing diagnostic context for mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cli/src/command_effects.rs`:
- Around line 153-165: The nothing_is_unclassified_by_accident test must compare
the complete set of commands with unset effects against UNCLASSIFIED in both
directions. Build the actual unclassified path set from commands(), assert it
equals the declared UNCLASSIFIED set, and retain the existing diagnostic context
for mismatches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: d9ff4fbc-177e-4773-b026-eaa6cf09faea
📒 Files selected for processing (16)
cli/src/command_effects.rscli/src/lib.rscli/src/usage_spec.rscli/usage.usage.kdldocs/cli/reference/commands.jsondocs/cli/reference/complete-word.mddocs/cli/reference/generate.mddocs/cli/reference/generate/completion-init.mddocs/cli/reference/generate/completion.mddocs/cli/reference/generate/fig.mddocs/cli/reference/generate/json.mddocs/cli/reference/generate/manpage.mddocs/cli/reference/generate/markdown.mddocs/cli/reference/generate/sdk.mddocs/cli/reference/lint.mddocs/cli/reference/sponsors.md
`list_commands` emitted `"daemons remove"` while `describe_command` answered with `"pitchfork daemons remove"`. An agent doing the obvious thing — read the list, describe an entry, describe something the first response mentioned — got a tool error on a command that exists, because `find_chain` read the binary name as a subcommand. `describe_command` now reports the path alone and carries `bin` beside it, the way `list_commands` already does. A test walks every row of `list_commands` through `describe_command` and asserts the name comes back unchanged, so the two ends cannot drift apart again. A leading binary name is also accepted now, since an agent that has seen the CLI in a shell writes the whole line. Only skipped when the root has no subcommand by that name, so a CLI with a `usage usage` keeps resolving its own command rather than losing it to the prefix. Rebased onto main, which brought #751. Its `nothing_is_unclassified_by_accident` test failed immediately on `mcp`, which is what it is for; classified `read`, since every tool this serves only reads the spec it was handed. Unlike `mise mcp`, which stays unclassified because it serves a tool that runs tasks, nothing here can act on the CLI it describes. Reported by cursor on #746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
⚠️ **CAUTION: this is a major update, indicating a breaking change!**⚠️ This MR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [usage](https://github.com/jdx/usage) | tools | major | `3.5.6` → `5.1.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/usage (usage)</summary> ### [`v5.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#510---2026-08-09) [Compare Source](jdx/usage@v5.0.0...v5.1.0) ##### 🚀 Features - **(spec)** parse usage comments from strings by [@​jdx](https://github.com/jdx) in [#​782](jdx/usage#782) ##### 🐛 Bug Fixes - **(spec)** avoid inferred metadata from included specs by [@​jdx](https://github.com/jdx) in [#​786](jdx/usage#786) ##### 🧪 Testing - **(windows)** make the suite runnable on Windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​771](jdx/usage#771) ##### 📦️ Dependency Updates - update rust crate rmcp to v3 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​780](jdx/usage#780) ### [`v5.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#500---2026-08-02) [Compare Source](jdx/usage@v4.1.0...v5.0.0) ##### 🚀 Features - **(cli)** allow overriding the shell program with USAGE\_SHELL\_<SHELL> by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​767](jdx/usage#767) ##### 🐛 Bug Fixes - **(cli)** forward parsed args to WSL bash via WSLENV on windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​764](jdx/usage#764) - **(cli)** let generate markdown write to stdout by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​766](jdx/usage#766) - **(complete)** use `type -P` so the CLI-presence guard ignores shell functions by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​760](jdx/usage#760) - **(parse)** enforce double\_dash="required" for positional args by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​762](jdx/usage#762) - **(windows)** run `run=` scripts with sh when available by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​765](jdx/usage#765) ##### 🎨 Styling - fix clippy and deprecation warnings in test and bench targets by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​763](jdx/usage#763) ### [`v4.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#410---2026-07-30) [Compare Source](jdx/usage@v4.0.0...v4.1.0) ##### 🚀 Features - **(cli)** declare what each usage command does to the world by [@​jdx](https://github.com/jdx) in [#​751](jdx/usage#751) - **(mcp)** serve a usage spec to an agent over stdio by [@​jdx](https://github.com/jdx) in [#​746](jdx/usage#746) - **(spec)** add a top-level `repository` field by [@​jdx](https://github.com/jdx) in [#​747](jdx/usage#747) ##### 🐛 Bug Fixes - **(parse)** keep a re-declared global's aliases on one flag by [@​jdx](https://github.com/jdx) in [#​752](jdx/usage#752) - complete repeated variadic args by [@​Jai-JAP](https://github.com/Jai-JAP) in [#​753](jdx/usage#753) ##### New Contributors - [@​Jai-JAP](https://github.com/Jai-JAP) made their first contribution in [#​753](jdx/usage#753) ### [`v4.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#400---2026-07-25) [Compare Source](jdx/usage@v3.6.0...v4.0.0) ##### 🚀 Features - **(spec)** allow effect= on flags and args by [@​jdx](https://github.com/jdx) in [#​742](jdx/usage#742) ### [`v3.6.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#360---2026-07-25) [Compare Source](jdx/usage@v3.5.7...v3.6.0) ##### 🚀 Features - **(spec)** add effect= to declare what a command does to the world by [@​jdx](https://github.com/jdx) in [#​739](jdx/usage#739) ##### 🚜 Refactor - **(spec)** make missed SpecCommand fields a compile error, and fix the four that were already missed by [@​jdx](https://github.com/jdx) in [#​740](jdx/usage#740) ### [`v3.5.7`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#357---2026-07-25) [Compare Source](jdx/usage@v3.5.6...v3.5.7) ##### 🐛 Bug Fixes - **(parse)** don't leak the mounting CLI's flags into mounted commands; scan past non-global flags by [@​jdx](https://github.com/jdx) in [#​738](jdx/usage#738) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODguMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWFqb3IiXX0=-->
usage added
effect=, shipped it to mise, hk, pitchfork, aube and communique — and declared nothing for its own 16 commands. Anything pointed at usage's spec sees empty fields on the tool that invented the feature.complete-wordgenerategenerate completiongenerate completion-initgenerate fig--out-file→ writegenerate jsongenerate manpage--out-file→ writegenerate markdown--out-dir,--out-file→ writegenerate sdklintsponsorsWhy the shape is interesting
Most of these print to stdout and take an optional flag to write to a file instead, so they sit at
readwith the flag raising them. That's the composition rule earning its place rather than being asserted in a doc —usage g markdown -f x.kdlonly reads; the same command with--out-filewrites.generate sdkis the exception. Its-o --outputis required, so there is no read-only way to invoke it and the effect belongs on the command, not a flag.Unclassified, deliberately
bash,fish,zsh,powershellandexecrun a script the user supplied, so their effect is whatever that script does. Labeling them would be a lie in whichever direction it was labeled, andreadin particular would be dangerous. A test asserts every unclassified command has an entry inUNCLASSIFIEDwith its reason, so a new command can't go unlabeled by accident.How
clap can't express this, so it's applied to the derived spec through
clap_usage::spec(#743), the same shape as mise'scommand_effects. Five tests cover stale command paths, stale flag names, the unclassified guard, and both composition cases above.Also emits
min_usage_version "4.0"— an olderusagerejects a spec carryingeffect=with "unsupported cmd prop effect", so the declaration and the floor move together. mise does the same.The generated docs pick it up for free:
usage generate sdknow reads Effect: modifies state, and--out-fileonmarkdownreads Effect: modifies state under a read-only command.This PR was generated by an AI coding assistant.
Note
Low Risk
Metadata and spec-generation path changes only; no runtime command behavior changes beyond richer
--usage-specoutput.Overview
Adds
command_effectsso theusageCLI’s own--usage-specoutput carrieseffect=read/effect=writeon commands and flags (clap can’t express this; it’s patched onto the derived spec like mise/hk).Most generators are read;
--out-file,--out-dir, etc. raise matching flags to write.generate sdkis write at the command level because output is required.bash,exec, and other script runners stay unclassified, with tests guarding stale paths and accidental gaps.usage_spec::generatenow builds the spec viaclap_usage::spec, runsapply, and emitsmin_usage_version "4.0"alongside the KDL. Regeneratedusage.usage.kdl,commands.json, and CLI reference docs show read-only vs modifies-state in docs.Reviewed by Cursor Bugbot for commit 9ee7a02. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Documentation