diff --git a/ROADMAP.md b/ROADMAP.md index 735315fe..4b89484e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7776,3 +7776,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 804. **`claw agents show ` and `claw skills show ` in text mode returned wrong `agent_not_found`/silent empty instead of catching extra args** — dogfooded 2026-05-27 on `bad1b97f`. Parity gap with JSON-mode fix #796: the text-mode show handlers in `commands/src/lib.rs` still used single-split `split_once(' ')` without checking for spaces in the extracted name. Fix: added `contains(' ')` guard to both text-mode show arms; extra tokens now return `unexpected extra arguments` with usage hint. 62 CLI contract tests pass. [SCOPE: claw-code] 805. **`claw skills show ` in text mode silently returned "No skills found." instead of an error** — dogfooded 2026-05-27 on `2c3c0f60`. The text-mode show handler in `handle_skills_slash_command` returned `render_skills_report(&matched)` with an empty vec instead of checking for empty match and returning an error. JSON mode already returned `skill_not_found` since #706. Fix: added `matched.is_empty()` guard with `skill_not_found` error + `\n` hint suggesting `claw skills list`. 62 CLI contract tests pass. [SCOPE: claw-code] + +806. **`claw plugins show ` in text mode returned "No plugins installed." instead of an error** — dogfooded 2026-05-27 on `ae6a207d`. The text-mode path in `print_plugins` printed `payload.message` (the full list render) without checking if the requested plugin existed. JSON mode correctly returned `plugin_not_found`. Fix: added show-action filtering + not-found guard to text-mode path; added `starts_with("plugin_not_found:")` arm to classifier for the new error prefix. 63 CLI contract tests pass. [SCOPE: claw-code] diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 225a27eb..14cfbba9 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -335,7 +335,7 @@ fn classify_error_kind(message: &str) -> &'static str { "unknown_agents_subcommand" } else if message.starts_with("agent not found:") { "agent_not_found" - } else if message.contains("is not installed") { + } else if message.contains("is not installed") || message.starts_with("plugin_not_found:") { "plugin_not_found" } else if message.contains("plugin source") && message.contains("was not found") { // #794: `plugins install /nonexistent/path` → "plugin source ... was not found" @@ -6406,7 +6406,28 @@ impl LiveCli { } let payload = plugins_command_payload_for(&cwd, action, target)?; match output_format { - CliOutputFormat::Text => println!("{}", payload.message), + CliOutputFormat::Text => { + // #806: text-mode show must return error when plugin not found (parity with JSON) + let action_str = action.unwrap_or("list"); + if matches!(action_str, "show" | "info" | "describe") { + if let Some(name) = target { + let needle = name.to_lowercase(); + let found = payload.plugins.iter().any(|p| { + p.get("id") + .and_then(|v| v.as_str()) + .map(|id| id.to_lowercase() == needle) + .unwrap_or(false) + }); + if !found { + return Err(format!( + "plugin_not_found: plugin '{}' not found\nRun `claw plugins list` to see available plugins.", + name + ).into()); + } + } + } + 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.