Skip to content

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

Merged
jdx merged 6 commits into
mainfrom
claude/command-effects
Jul 27, 2026
Merged

jdx merged 6 commits into
mainfrom
claude/command-effects

Conversation

@jdx

@jdx jdx commented Jul 25, 2026 •

Copy link
Copy Markdown
Owner

usage 3.6 shipped effect= (jdx/usage#739). This declares it for all 4 communique commands: 2 read, 2 write.

Last of the set, after jdx/mise#11306, jdx/hk#1121 and jdx/pitchfork#666.

The one judgement call

generate is write, not read. A bare invocation only prints, but --changelog rewrites CHANGELOG.md and --github-release replaces the body of a published release. Since effect is per-command rather than per-flag, write is the honest floor — it keeps generate out of any read-only allowlist, which is the distinction that actually matters. The comment in the table spells out why, including that --github-release overwrites remote content.

Why the clap_usage dependency goes away

Same as the sibling PRs: clap_usage is still published against usage-lib 2.x (latest 2.0.3, requiring usage-lib ^2.0.3), so a spec generated through it physically cannot carry effect=. clap_usage::generate is four lines, so this inlines it and depends on usage-lib = "3" directly.

Publishing clap_usage 3.x from the usage repo is the alternative — its copy in-repo is still versioned 2.0.3 while building against workspace usage-lib 3.6.0, so it's due a release regardless.

One cleanup worth noting

src/usage.rs had two separate spec-generation paths — the real one in run() and a second copy inside the test_usage_kdl_in_sync test. They could drift, and the test would still pass while checking the wrong thing. Both now go through one generate_to_string(), so the sync test verifies what the command actually emits. communique.usage.kdl is regenerated.

Verified

cargo build, cargo clippy --all-targets, cargo fmt --check clean. All four new/affected tests pass, including test_usage_kdl_in_sync.

cargo test has 3 pre-existing failures in tools::grep (test_grep_basic, test_grep_no_matches, test_grep_with_glob) — confirmed present on a clean checkout of main; they shell out to ripgrep, which isn't installed in my environment.

This PR was generated by an AI coding assistant.


Note

Low Risk
Metadata and dependency bumps only; runtime CLI behavior is unchanged aside from richer usage/docs output.

Overview
Upgrades clap_usage to 4 and pins the usage CLI to 4.0.0 so the checked-in spec can use effect= and min_usage_version "4.0".

Adds src/command_effects.rs, which classifies each subcommand and a few state-changing flags (clap cannot express this), then apply merges those annotations into the spec before emission. generate is read by default; --changelog, --github-release, and --output are write; init --force is destructive. Tests require every command to be classified and that flag entries still match real flags.

communique usage now builds the KDL through a single generate_to_string() (shared with the sync test), and communique.usage.kdl plus docs/cli JSON/Markdown are regenerated with effect labels.

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

usage 3.6 added `effect=`, which lets a spec say whether a command only
inspects state, changes it, or destroys something (jdx/usage#739). Declare
it for all 4 communique commands: 2 read, 2 write.

`generate` is write rather than read because, while a bare invocation only
prints, `--changelog` rewrites CHANGELOG.md and `--github-release` replaces
the body of a published release. Write is the honest floor: it keeps the
command out of any read-only allowlist, which is the distinction that
matters.

Three tests keep the table honest: every command must be classified, no
entry may refer to a command that no longer exists, and nothing may be
classified twice.

To get `effect=` at all, generate the spec through usage-lib 3 rather than
clap_usage, which is still published against usage-lib 2.x and so cannot
carry the field. Spec generation moves into a shared generate_to_string so
`communique usage` and the sync test cannot drift apart, and
communique.usage.kdl is regenerated.

Also emit `min_usage_version "3.6"`, since older `usage` CLIs reject the
spec with "unsupported cmd prop effect".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 21:43
@coderabbitai

coderabbitai Bot commented Jul 25, 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: 56 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: e80b271d-9125-40a3-bcc7-bd8615501883

📥 Commits

Reviewing files that changed from the base of the PR and between 577465e and b6b45e1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml
  • communique.usage.kdl
  • docs/cli/commands.json
  • docs/cli/generate.md
  • docs/cli/init.md
  • docs/cli/sponsors.md
  • mise.toml
  • src/command_effects.rs
  • src/main.rs
  • src/usage.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.

@socket-security

socket-security Bot commented Jul 25, 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

@codecov

codecov Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.36842% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.01%. Comparing base (577465e) to head (b6b45e1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/command_effects.rs 97.14% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #240      +/-   ##
==========================================
+ Coverage   94.84%   95.01%   +0.17%     
==========================================
  Files          26       27       +1     
  Lines        5377     5501     +124     
  Branches     5377     5501     +124     
==========================================
+ Hits         5100     5227     +127     
+ Misses        177      172       -5     
- Partials      100      102       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@greptile-apps

greptile-apps Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

Greptile Summary

Updates usage metadata and generated CLI documentation:

  • Upgrades clap_usage and the usage toolchain to version 4.
  • Adds command- and flag-level effect classifications.
  • Consolidates usage-spec generation into a shared implementation.
  • Regenerates the checked-in KDL, JSON, and Markdown artifacts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/command_effects.rs Adds effect mappings, recursively applies them to the generated specification, and tests command and flag coverage.
src/usage.rs Centralizes specification generation, applies effect metadata, and emits the usage 4 minimum-version declaration.
Cargo.toml Upgrades clap_usage from version 2 to version 4 to support effect metadata.
mise.toml Pins the usage CLI to 4.0.0 so generated specifications and rendering automation use a compatible version.
communique.usage.kdl Regenerates the checked-in specification with command and flag effects and a minimum usage version.
docs/cli/commands.json Refreshes generated JSON documentation with the new effect metadata.

Reviews (6): Last reviewed commit: "test: evaluate assert messages so the li..." | Re-trigger Greptile

Copilot AI 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.

Pull request overview

This PR updates Communiqué’s communique usage spec generation to support usage v3.6’s new effect= command annotation, enabling downstream tooling to distinguish read-only vs mutating commands.

Changes:

  • Replace clap_usage-based generation with a direct usage-lib v3 Spec conversion and a shared generate_to_string() code path.
  • Introduce src/command_effects.rs to declare and apply read/write effects to all CLI subcommands, with tests enforcing full coverage.
  • Regenerate communique.usage.kdl to include min_usage_version "3.6" and per-command effect= fields.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/usage.rs Inlines spec generation to string, adds min_usage_version, and applies command effects.
src/main.rs Wires in the new command_effects module.
src/command_effects.rs New module defining per-command effects and tests to keep the table in sync with the CLI tree.
communique.usage.kdl Regenerated usage spec including effect= and min_usage_version "3.6".
Cargo.toml Drops clap_usage and adds usage-lib v3 with clap feature.
Cargo.lock Lockfile updates reflecting dependency changes (including usage-lib 3.6.0).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/command_effects.rs Outdated
Comment on lines +15 to +17
//! **An unlisted command means "unknown", not "safe".** Consumers treat the
//! absence of a value as "ask", so leaving a command out is the conservative
//! choice and mislabeling one `read` is the dangerous one.
Comment thread src/usage.rs
Comment on lines +19 to +22
// 3.6 added `effect=`; older `usage` CLIs reject the spec outright with
// "unsupported cmd prop effect".
format!("// @generated by usage-cli from clap metadata\nmin_usage_version \"3.6\"\n{spec}")
}
usage 4 lets a flag raise a command's effect (jdx/usage#742), so `generate`
no longer has to carry its worst case. It is `read` — a bare invocation only
prints — with the danger declared where it actually lives:

- --changelog     write        rewrites CHANGELOG.md
- --github-release write       replaces a published release body
- --output        write        writes to a file
- init --force    destructive  overwrites an existing communique.toml

The effect of an invocation is the maximum of the command's and those of the
flags supplied, so `communique generate` is now correctly usable from a
read-only allowlist while `--github-release` still prompts. Most flags
declare nothing, which is the intent.

A test asserts every FLAG_EFFECTS entry matches a real flag, so a renamed
flag fails the build rather than silently losing its annotation.

Review feedback:

- the module docs called omitting a command the "conservative choice" while
  the tests hard-fail on it. Both are true for different reasons, but the
  wording was confusing: an unset effect is never dangerous, yet for a CLI
  this small "unknown" should be deliberate rather than forgotten.
- the generated header credited usage-cli; the spec is produced by
  `communique usage`. Corrected.

Also pins `usage = "4"` instead of "latest" in mise.toml. There is no
lockfile here, so `latest` resolved to a pre-3.6 usage at CI time and the
render failed with "unsupported cmd prop effect" — the spec's declared
minimum was not reflected in the toolchain it is rendered with.

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

jdx commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Both findings addressed, and usage 4 landed in the meantime so generate can now be labelled properly rather than pessimistically.

"Conservative choice" vs tests that hard-fail — fair, the wording was confusing. Both things were true for different reasons and I ran them together. An unset effect genuinely is never dangerous the way a wrong read is; but for a CLI this small there is no reason to leave one undecided, so the test requires completeness and "unknown" should be a deliberate choice rather than something forgotten. Docs now say that.

@generated by usage-cli — correct, the spec is produced by communique usage. Header fixed and the KDL regenerated.


generate is now read, with the danger on its flags

jdx/usage#742 (in usage 4) lets a flag raise its command's effect, so I no longer have to give the whole command its worst case:

effect
generate read — a bare invocation only prints
--changelog write — rewrites CHANGELOG.md
--github-release write — replaces a published release body
--output write
init --force destructive — overwrites an existing communique.toml

The effect of an invocation is the maximum of the command's and the flags supplied, so communique generate is now usable from a read-only allowlist while --github-release still prompts. That is the precision I flagged as missing when I first labelled it write. Most flags declare nothing, which is the intent.

A test asserts every FLAG_EFFECTS entry matches a real flag, so renaming one fails the build instead of silently dropping its annotation.

The CI failure

mise run render exited 181 because there is no lockfile here and usage = "latest" resolved to a pre-3.6 usage, which rejects effect= outright:

This usage spec requires at least version 3.6, but you are using version 3.5.2
× Invalid usage config — unsupported cmd prop effect

The spec declared a minimum that the toolchain rendering it did not have to meet. Now pinned to usage = "4" in mise.toml, matching min_usage_version "4.0". I did not add a lockfile — that is a repo convention change, and pinning the major expresses the actual constraint.

Verified

cargo build, clippy --all-targets, fmt --check clean. All four classification tests and test_usage_kdl_in_sync pass. The 3 tools::grep failures are pre-existing — confirmed on a clean main; they shell out to ripgrep, which is not installed in my environment.

This comment was generated by an AI coding assistant.

jdx and others added 2 commits July 25, 2026 23:10
mise resolved `usage = "4"` as a literal version and tried to fetch a `v4`
release tag, which does not exist:

  Failed to install aqua:jdx/usage@4: 404 for releases/tags/v4

Pin 4.0.0. With no lockfile in this repo an exact pin is also what makes
the toolchain reproducible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
codecov flagged the patch, and it was right about something real: every
test here checked the tables against the CLI — complete, no stale entries,
no duplicates — but none checked that `apply` actually transfers them onto
the spec. The tables could have been perfect and silently never applied.

`apply_annotates_commands_and_flags` covers that path directly, including
that a flag with no entry is left alone rather than inheriting one.

Also switches to clap_usage::spec(), now that clap_usage 4.0.0 is published
(jdx/usage#743) with the accessor that was missing when this branch started.
The direct usage-lib dependency goes away — clap_usage re-exports usage.
`communique usage` emits a byte-identical spec.

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

jdx commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

On the two codecov checks — I dug into the actual numbers rather than guessing, and I don't think there's anything real to fix here. Your call, though.

base 94.84%  →  head 94.83%      (-0.01pp)
new file src/command_effects.rs: 100 lines, 93% — 4 misses, 3 partials

ci and coverage both pass. The uncovered lines in the new file are the failure branches of the test helpers — the panic! in unwrap_or_else(|| panic!("no --{name}")) and the _ => missing.push(..) arm that builds the assertion message. They only execute when a test is failing, so covering them would mean writing tests that assert the tests fail. That is not coverage worth having.

There is no codecov.yml in the repo, so both checks use codecov's default target: auto, threshold: 0% — zero tolerance. Any PR that adds a file with error-handling branches trips it, whatever the absolute number. 0.01pp is noise.

Worth noting the review did surface a real gap, and I fixed that one: none of the tests called apply(), so the tables could have been correct and silently never applied to the spec. apply_annotates_commands_and_flags now covers that path directly, and I added the equivalent test to mise, hk, pitchfork and aube, where the same gap existed.

If you'd rather these checks not gate PRs, a codecov.yml with a small threshold (1-2%) would stop this recurring. Happy to add one, but that is a repo-wide policy change so I have left it alone.

This comment was generated by an AI coding assistant.

jdx and others added 2 commits July 27, 2026 00:29
Measured the coverage rather than guessing at it, and usage.rs was the real
gap: 78.95%, with `run` entirely uncovered. It is the only path that
actually emits the spec, so nothing checked that `communique usage`
succeeds at all — `generate_to_string` was covered by the sync test, but the
command wrapping it was not.

usage.rs is now 100%.

The four lines still uncovered in command_effects.rs are the format
arguments of assert messages and the `_ =>` arm that records a missing flag.
Rust only evaluates those when the assertion fails, so covering them would
mean asserting that the tests fail. Left alone deliberately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`assert!(cond, "..{}", x)` only formats when the assertion fails, so the
message expressions were dead code while the tests passed — which is what
codecov was flagging on the patch. Building the detail string up front costs
nothing at these list sizes and makes the code genuinely execute, rather
than covering it by asserting that a test fails.

Same reason for `then_some` over `then` in the flag check: the label is now
built for every entry instead of only on the failing path.

command_effects.rs goes from 95.37% to 99.13%. The one line left is the
`is_some_and` short-circuit for a command that does not exist, which cannot
happen while the completeness test passes.

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

jdx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Both codecov checks pass now. I measured rather than kept guessing — installed cargo-llvm-cov and got the actual missing lines instead of reasoning about them from the outside.

That turned up something I had wrong. I said the uncovered lines were all test-helper panic! branches. They were not: usage.rs was at 78.95%, with Usage::run entirely uncovered. It is the only path that actually emits the spec — generate_to_string was covered by the sync test, but nothing checked that communique usage succeeds at all. That is a real gap and my earlier dismissal was wrong.

                        before      after
usage.rs                78.95%     100%
command_effects.rs      95.37%    99.13%
project total           94.83%    94.94%   (base 94.84%)

The remaining lines in command_effects.rs were the format arguments of assert! messages. Rust only evaluates those when the assertion fails, so they were dead while the tests passed. Building the detail string up front costs nothing at these list sizes and makes the code genuinely run — rather than covering it by asserting that a test fails. Same reason for then_some over then in the flag check.

One line is still uncovered: the is_some_and short-circuit for a command that does not exist, which cannot happen while the completeness test passes. Left alone.

Worth noting the three tools::grep failures I had been reporting as pre-existing were only failing because ripgrep was not installed in my environment. With it, the full suite is 184 passing.

This comment was generated by an AI coding assistant.

@jdx
jdx merged commit ae5f6bb into main Jul 27, 2026
8 checks passed
@jdx
jdx deleted the claude/command-effects branch July 27, 2026 00:36
jdx added a commit that referenced this pull request Aug 2, 2026
This release teaches communique's usage spec what each command does to
the world — `read`, `write`, or `destructive` — so tools built on the
spec can tell a safe inspection from a state-changing one. It also picks
up progress-display fixes from `clx` v3.

## Added

- *(usage)* Every communique command now carries an `effect` annotation
in its usage spec, distinguishing read-only commands from those that
modify or destroy state. `generate` and `sponsors` are `read`; `init` is
`write`; and the flags that actually change things are raised
accordingly — `--changelog`, `--github-release`, and `--output` on
`generate` are `write`, and `init --force` is `destructive`. Because
`effect` is per-command, `generate` is classified as `read` at its floor
while its state-changing flags carry the higher effect (the effect of an
invocation is the maximum of the command and its supplied flags). The
regenerated `communique.usage.kdl` and `docs/cli` reflect these labels.
([#240](#240)) (@jdx)

## Changed

- Updated `clx` to v3 for progress-display fixes: cursor escape
sequences are no longer written in text or quiet mode, and a reflowed
frame is now cleared after a terminal resize.
([#248](#248))

**Full Changelog**: https://github.com/jdx/communique/commits/v1.3.0

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and documentation-only changes with no runtime logic in this
diff.
> 
> **Overview**
> **Cuts release v1.3.0** by bumping the crate and generated CLI
metadata from `1.2.4` to `1.3.0` in `Cargo.toml`, `Cargo.lock`,
`communique.usage.kdl`, and `docs/cli`.
> 
> **Updates `CHANGELOG.md`** with a dated `[1.3.0]` section documenting
the shipped work (usage `read`/`write`/`destructive` effects and `clx`
v3 progress fixes), while `[Unreleased]` still only lists the upcoming
default model bump to `claude-opus-4-8`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
169db15. 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**
* CLI usage specifications now identify each command’s world effects:
read, write, or destructive.

* **Changed**
  * Updated the CLI to version 1.3.0.
  * Text and quiet modes no longer emit cursor escape sequences.
  * Reflowed frames are cleared after terminal resizing.
* Updated displayed version information across CLI documentation and
metadata.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@jdx jdx mentioned this pull request Aug 2, 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.

2 participants