From 1d5db5f77db682ddaead874bd6ace86f5d292cbf Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 26 May 2026 18:04:04 +0900 Subject: [PATCH] fix(#743): plugins help --output-format json now emits usage envelope matching agents/mcp/skills help shape; resolves #420 --- ROADMAP.md | 2 ++ rust/crates/commands/src/lib.rs | 7 +++++++ rust/crates/rusty-claude-cli/src/main.rs | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 9804074a..98bafe47 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7651,3 +7651,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 741. **`claw config list`, `claw config show`, `claw config bogus` --output-format json returned `hint: null` — the unsupported_config_section error envelope had no `hint` field populated, so callers reading `.hint` get null with no actionable guidance** — dogfooded 2026-05-26 on `5d072d21`. The `render_config_json` unsupported-section branch returned a JSON object with `error` (contains the section list) but no `hint` field. Notably `config list` and `config show` are natural verb patterns that users type expecting a list/show subcommand, but claw config uses `claw config` (no args) for list and `claw config
` for show — the error gave no indication of this. Fix: add `hint` field to unsupported_config_section error; verbs (`list`, `show`, `help`, `info`) get a hint explaining the correct idiom (`claw config` / `claw config
`); other unknown sections get a "not a config section" hint listing valid values. Source: Jobdori dogfood on `5d072d21`, 2026-05-26. 742. **ROADMAP #740 test coverage gap: the new `changed_file_count` branch for git repos was unreachable — the fixture is a plain `unique_temp_dir` (no `git init`), so the test always exercises the `no_git_repo` path and never proves the numeric contract or deduplication behavior** — confirmed by gaebal-gajae on `5d072d21`, fixed on `6e78c1fc`. Fix: add `diff_json_changed_file_count_deduplication_733` test that (a) `git init`s a temp repo, (b) commits a file, (c) asserts `result:"clean"` + `changed_file_count:0`, (d) stages an edit + makes an unstaged edit to the same file, (e) asserts `result:"changes"` + `changed_file_count:1` — proving the BTreeSet deduplication actually works. Source: gaebal-gajae dogfood on `5d072d21`, 2026-05-26. + +743. **`claw plugins help --output-format json` returned `error_kind:"unknown_plugins_action"` with `hint:null` instead of the usage envelope (`action:"help", status:"ok", unexpected:null, usage:{...}`) that `agents help`, `mcp help`, and `skills help` all emit — schema drift within the same command family (ROADMAP #420)** — dogfooded 2026-05-26 on `2036f0bd`. Fix: (a) added `Some("help" | "-h" | "--help")` arm to `handle_plugins_slash_command` returning a text usage message (text path parity); (b) added early-return JSON help envelope in `print_plugins` JSON path matching shape of agents/mcp help: `{action:"help", kind:"plugin", status:"ok", unexpected:null, usage:{direct_cli, slash_command}}`. Source: Jobdori dogfood on `2036f0bd`, 2026-05-26. diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index 05fad9e2..23fff3bc 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -2334,6 +2334,13 @@ pub fn handle_plugins_slash_command( reload_runtime: false, }) } + // #743/#420: "help" was caught by Some(other) → unknown_plugins_action error with hint:null. + // agents/mcp/skills all return a help envelope; plugins must match that parity. + Some("help" | "-h" | "--help") => Ok(PluginsCommandResult { + message: "Plugins\n Usage /plugins [list|show |install |enable |disable |uninstall |update |help]\n Subcommands list show install enable disable uninstall update help" + .to_string(), + reload_runtime: false, + }), Some(other) => Err(PluginError::CommandFailed(format!( "unknown_plugins_action: '{other}' is not a supported /plugins action. Use list, show, install, enable, disable, uninstall, or update." ))), diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index ef7be876..9a62b867 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -6108,6 +6108,23 @@ impl LiveCli { CliOutputFormat::Text => println!("{}", payload.message), CliOutputFormat::Json => { let action_str = action.unwrap_or("list"); + // #743/#420: plugins help must return a usage envelope matching agents/mcp/skills help shape. + if matches!(action_str, "help" | "-h" | "--help") { + let cwd_str = cwd.display().to_string(); + let obj = json!({ + "kind": "plugin", + "action": "help", + "status": "ok", + "unexpected": null, + "usage": { + "direct_cli": "claw plugins [list|show |install |enable |disable |uninstall |update |help]", + "slash_command": "/plugins [list|show |install |enable |disable |uninstall |update |help]", + }, + "cwd": cwd_str, + }); + println!("{}", serde_json::to_string_pretty(&obj)?); + return Ok(()); + } // For show/info/describe, filter to the named plugin (exact match). // For list with a target, treat target as a substring filter. let is_show_action = matches!(action_str, "show" | "info" | "describe");