Skip to content

feat(usage): declare what each command does to the world - #1122

Merged
jdx merged 2 commits into
mainfrom
claude/command-effects
Jul 26, 2026
Merged

feat(usage): declare what each command does to the world#1122
jdx merged 2 commits into
mainfrom
claude/command-effects

Conversation

@jdx

@jdx jdx commented Jul 26, 2026

Copy link
Copy Markdown
Owner

usage 4 shipped effect= (jdx/usage#739, #742), which lets a spec say whether a command only inspects state, changes it, or destroys something. This declares it for all 114 aube commands.

60 read · 37 write · 6 destructive · 11 deliberately unclassified

Completes the set alongside jdx/mise#11306, jdx/hk#1121, jdx/pitchfork#666 and jdx/communique#240.

Two rules, both in the module docs

Fetching or extracting a package is not an effect. Nearly every command here may populate the store or the metadata cache on the way to its real job. Counting that would make every command write and leave the field with no signal. Same for node_modules, which is reproducible from the lockfile — clean and ci delete it and are still only write.

Registry commands act on everyone. This is what makes aube different from the other four: publish, deprecate, dist-tag and access change what every consumer of a package sees, not just this machine.

They follow the same rules as everything else — unpublish removes so it is destructive, publish creates so it is write — but none are read, and each carries a comment about blast radius, because the tier alone understates it:

unpublish destructive — every consumer loses it, and most registries won't let you republish the same version
dist-tag rm destructive — removes a tag consumers resolve through
access revoke destructive
publish write by the letter of the rules, but a published version cannot be replaced. The comment says never auto-run it.
dist-tag add write — repoints a tag every consumer resolves through, latest included
deprecate write — visible to all consumers, but undeprecate undoes it

Worth saying plainly: the three-value vocabulary doesn't distinguish "destroys my stuff" from "destroys everyone's stuff". I kept to the letter of the definitions rather than inflating tiers per-command, since consistency across five CLIs is worth more than a bespoke judgement here — but publish sitting at write is the place where that shows, and it's flagged in the source.

Unclassified is a real answer

11 commands — run, exec, dlx, node, create, recursive, install-test and the script shortcuts — execute code that isn't aube's: a package's lifecycle scripts, a binary fetched from the registry, a script from package.json. Their effect is whatever that code does, and read would be actively dangerous.

Tests

Four: every command classified, no stale entries, no duplicates, and apply actually transferring the table onto the spec. That last one matters — the other three check the table against the CLI, so without it the table could be perfect and silently never applied. Coverage walks hidden commands too, since __node-gyp-bootstrap and the npm-compat aliases still run.

Also

  • switches to clap_usage::spec() — clap_usage 4.0.0 exposes the Spec before rendering, which is what makes annotating it possible at all; generate() writes straight to a writer
  • emits min_usage_version "4.0", since older usage CLIs reject the spec with unsupported cmd prop effect
  • bumps usage 3.2.0 → 4.0.0 in mise.lock so the pinned CLI can read what aube emits

Verified

cargo build -p aube, cargo clippy -p aube --all-targets, cargo fmt --check clean. All four classification tests pass. mise run render regenerated aube.usage.kdl and the CLI docs.

This PR was generated by an AI coding assistant.


Note

Low Risk
Mostly declarative usage metadata and dependency lock updates; no change to default command behavior unless external tools start acting on effect=.

Overview
Adds usage 4 effect= metadata for every aube command so tooling can tell inspect-only work from local mutations and irreversible/registry-wide actions.

A new command_effects module holds the classification table (with documented rules: store/cache fetches are not effects; registry commands are never read; script runners stay unclassified). print_usage_spec now uses clap_usage::spec(), calls command_effects::apply, and emits min_usage_version "4.0" in generated aube.usage.kdl. Tests enforce full CLI coverage and that effects land on the spec.

clap_usage / usage-lib are bumped to 4.0.0 (reflected in Cargo.lock: drops the old tera 1.x template stack, consolidates windows-sys, updates roff). The pinned usage CLI in mise.lock is updated so render stays compatible.

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

usage 4 added `effect=`, which lets a spec say whether a command only
inspects state, changes it, or destroys something (jdx/usage#739, #742).
Declare it for all 114 aube commands: 60 read, 37 write, 6 destructive.

clap cannot express this, so the classification lives in one table in
crates/aube/src/command_effects.rs and is applied to the derived spec.
Matches what mise, hk, pitchfork and communique now do.

Two rules are worth stating, and both are in the module docs:

Fetching or extracting a package is not an effect. Nearly every command
here may populate the store or the metadata cache on the way to its real
job. Counting that would make every command `write` and leave the field
with no signal. The same goes for node_modules, which is reproducible from
the lockfile — `clean` and `ci` delete it and are still only `write`.

Registry commands act on everyone. `publish`, `deprecate`, `dist-tag` and
`access` change what every consumer of a package sees. They follow the same
rules as everything else — `unpublish` removes, so it is destructive;
`publish` creates, so it is write — but none are `read`, and each carries a
comment about the blast radius, because the tier alone understates it. A
published version cannot be replaced and `unpublish` takes it away from
everyone.

11 commands are deliberately unclassified: `run`, `exec`, `dlx`, `node`,
`create` and the script shortcuts execute code that is not aube's, so their
effect is whatever that code does.

Four tests keep the table honest: every command classified, no stale
entries, no duplicates, and `apply` actually transferring them onto the
spec. Coverage includes hidden commands, since those still run.

Also switches to clap_usage::spec() — clap_usage 4.0.0 exposes the Spec
before rendering, which is what makes annotating it possible — emits
min_usage_version "4.0" since older CLIs reject the field, and bumps usage
to 4.0.0 in mise.lock so the pinned CLI can read what aube emits.

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

coderabbitai Bot commented Jul 26, 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: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: b44f5dbd-7738-4802-8a1a-9c1fe54a072e

📥 Commits

Reviewing files that changed from the base of the PR and between 5c62780 and d2892fe.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • mise.lock is excluded by !**/*.lock
📒 Files selected for processing (98)
  • aube.usage.kdl
  • crates/aube/Cargo.toml
  • crates/aube/src/command_effects.rs
  • crates/aube/src/lib.rs
  • crates/aube/src/main.rs
  • docs/cli/access.md
  • docs/cli/access/get.md
  • docs/cli/access/get/status.md
  • docs/cli/access/grant.md
  • docs/cli/access/list.md
  • docs/cli/access/list/collaborators.md
  • docs/cli/access/list/packages.md
  • docs/cli/access/ls.md
  • docs/cli/access/revoke.md
  • docs/cli/access/set.md
  • docs/cli/activate.md
  • docs/cli/add.md
  • docs/cli/approve-builds.md
  • docs/cli/audit.md
  • docs/cli/bin.md
  • docs/cli/bugs.md
  • docs/cli/cache.md
  • docs/cli/cache/delete.md
  • docs/cli/cache/list-registries.md
  • docs/cli/cache/list.md
  • docs/cli/cache/prune.md
  • docs/cli/cache/view.md
  • docs/cli/cat-file.md
  • docs/cli/cat-index.md
  • docs/cli/check.md
  • docs/cli/ci.md
  • docs/cli/clean.md
  • docs/cli/commands.json
  • docs/cli/completion.md
  • docs/cli/config.md
  • docs/cli/config/delete.md
  • docs/cli/config/explain.md
  • docs/cli/config/find.md
  • docs/cli/config/get.md
  • docs/cli/config/list.md
  • docs/cli/config/set.md
  • docs/cli/config/tui.md
  • docs/cli/dedupe.md
  • docs/cli/deploy.md
  • docs/cli/deprecate.md
  • docs/cli/deprecations.md
  • docs/cli/diag.md
  • docs/cli/diag/analyze.md
  • docs/cli/diag/compare.md
  • docs/cli/dist-tag.md
  • docs/cli/dist-tag/add.md
  • docs/cli/dist-tag/ls.md
  • docs/cli/dist-tag/rm.md
  • docs/cli/doctor.md
  • docs/cli/fetch.md
  • docs/cli/find-hash.md
  • docs/cli/ignored-builds.md
  • docs/cli/import.md
  • docs/cli/init.md
  • docs/cli/install.md
  • docs/cli/licenses.md
  • docs/cli/link.md
  • docs/cli/list.md
  • docs/cli/login.md
  • docs/cli/logout.md
  • docs/cli/outdated.md
  • docs/cli/pack.md
  • docs/cli/patch-commit.md
  • docs/cli/patch-remove.md
  • docs/cli/patch.md
  • docs/cli/peers.md
  • docs/cli/peers/check.md
  • docs/cli/prefix.md
  • docs/cli/prune.md
  • docs/cli/publish.md
  • docs/cli/purge.md
  • docs/cli/query.md
  • docs/cli/rebuild.md
  • docs/cli/remove.md
  • docs/cli/root.md
  • docs/cli/runtime.md
  • docs/cli/runtime/list.md
  • docs/cli/runtime/set.md
  • docs/cli/sbom.md
  • docs/cli/sponsors.md
  • docs/cli/stage.md
  • docs/cli/store.md
  • docs/cli/store/add.md
  • docs/cli/store/path.md
  • docs/cli/store/prune.md
  • docs/cli/store/status.md
  • docs/cli/undeprecate.md
  • docs/cli/unlink.md
  • docs/cli/unpublish.md
  • docs/cli/update.md
  • docs/cli/version.md
  • docs/cli/view.md
  • docs/cli/why.md

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.

@socket-security

socket-security Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedcargo/​clap_usage@​2.0.3 ⏵ 4.0.010010093100100

View full report

Comment thread crates/aube/src/command_effects.rs Outdated
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds command-effect metadata throughout the generated usage specification.

  • Classifies commands as read, write, destructive, or deliberately unclassified.
  • Correctly reclassifies activate as write and logout as destructive, resolving both previous findings.
  • Upgrades the usage tooling to 4.0, applies effects before rendering, and regenerates CLI documentation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; both previously reported effect-classification defects are corrected in the source and generated specification.

Important Files Changed

Filename Overview
crates/aube/src/command_effects.rs Defines and applies command effects; the prior activate and logout misclassifications are fully corrected.
crates/aube/src/main.rs Builds a mutable usage specification, applies command effects, and renders the annotated result.
aube.usage.kdl Regenerated specification includes usage 4.0 metadata and the corrected command effects.
crates/aube/Cargo.toml Upgrades clap_usage to the API version required for mutable specification generation.
Cargo.lock Records the clap_usage and usage-lib 4.0 dependency resolution and associated transitive updates.

Reviews (2): Last reviewed commit: "fix: activate writes shims, logout destr..." | Re-trigger Greptile

Comment thread crates/aube/src/command_effects.rs Outdated
Comment thread crates/aube/src/command_effects.rs Outdated
Three review findings, all correct:

`activate` was `read` by analogy with other CLIs, where it only prints shell
code. aube's also calls `ensure_shims`, which creates the shim directory and
writes the tool shims into it. Now `write`.

`logout` was `write` on the grounds that logging back in restores the token.
But getting it back means authenticating again, which is exactly the
"redoing work" that makes `config delete` destructive — and this is
`config delete` for the auth key. Now destructive.

`publish` is `#[cfg(feature = "publish")]`, so `--no-default-features` drops
the subcommand while the table still claimed it, failing the stale-entry
test there. Moved to a FEATURE_EFFECTS table gated the same way.

`config tui` looked like the same case and is not: a
`cfg(not(feature = "config-tui"))` stub keeps the subcommand present either
way, it just errors when invoked. Verified by running the tests under
`--no-default-features` — which is how the difference showed up, since
gating it there made the completeness test fail.

Both feature configurations now pass.

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

@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 d2892fe. Configure here.

("set-script", Read),
("stage", Read),
("token", Read),
("whoami", Read),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

npm fallback mislabeled read

Medium Severity

The npm-compat stubs (owner, pkg, search, set-script, stage, token, whoami) are annotated effect=read based on the error-only path, but when npmPath is configured they delegate to the real npm binary and can perform registry writes or other side effects. That contradicts the module’s rule that mislabeling read is dangerous and that unknown behavior should stay unclassified.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d2892fe. Configure here.

@jdx

jdx commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

All three findings were correct; fixed.

activate mislabeled read (greptile) — I labelled it by analogy with the other CLIs, where activate only prints shell code. aube's also calls ensure_shims, which creates the shim directory and writes the tool shims into it. Now write. Good catch — it is precisely the dangerous direction.

logout should be destructive (greptile) — agreed, and my justification was the weak part. I wrote "logging back in restores it", but getting it back means authenticating again, which is the same "redoing work" that makes config delete destructive. This is config delete for the auth key, so it should be classified the same way. Now destructive.

publish ignores its feature gate (cursor) — correct, and it would only have shown up under --no-default-features. Moved to a FEATURE_EFFECTS table gated with #[cfg(feature = "publish")], the same shape mise uses for its unix-only and debug-only commands.

That one had a twist worth recording. I assumed config tui was the same case, since config-tui is also a default feature — but it is not: a cfg(not(feature = "config-tui")) stub keeps the subcommand present either way and just errors when invoked. Gating it made the completeness test fail under --no-default-features, which is how the difference surfaced. It stays unconditional, with a comment saying why.

Verified under both configurations:

cargo test -p aube command_effects                                  ok
cargo test -p aube --no-default-features --features rustls,hickory-dns   ok

This comment was generated by an AI coding assistant.

@jdx
jdx merged commit ae9f5ca into main Jul 26, 2026
38 checks passed
@jdx
jdx deleted the claude/command-effects branch July 26, 2026 00:45
@cursor cursor Bot mentioned this pull request Jul 26, 2026
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