From 9dd7e79eb2d697f2a3e2e6932d727a3cd3e7fe82 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 23 Apr 2026 02:07:50 +0900 Subject: [PATCH] fix(#130e-B): route plugins/prompt --help to dedicated help topics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Was Broken (ROADMAP #130e Category B) Two remaining surface-level help outliers after #130e-A: $ claw plugins --help Unknown /plugins action '--help'. Use list, install, enable, disable, uninstall, or update. $ claw prompt --help claw v0.1.0 (top-level help — wrong help topic) `plugins` treated `--help` as an invalid subaction name. `prompt` was explicitly listed in the early `wants_help` interception with commit/pr/issue, which routed to top-level help instead of prompt-specific help. ## Root Cause (Traced) 1. **plugins**: `parse_local_help_action()` didn't have a "plugins" arm, so `["plugins", "--help"]` returned None and continued into the `"plugins"` parser arm (main.rs:1031), which treated `--help` as the `action` argument. Runtime layer then rejected it as "Unknown action". 2. **prompt**: At main.rs:~800, there was an early interception for `--help` following certain subcommands (prompt, commit, pr, issue) that forced `wants_help = true`, routing to generic top-level help instead of letting parse_local_help_action produce a prompt-specific topic. ## What This Fix Does Same pattern as #130c/#130d/#130e-A: 1. **LocalHelpTopic enum extended** with Plugins, Prompt variants 2. **parse_local_help_action() extended** to map both new cases 3. **Help topic renderers added** with accurate usage info 4. **Early prompt-interception removed** — prompt now falls through to parse_local_help_action like other subcommands. commit/pr/issue (which aren't actual subcommands yet) remain in the early list. ## Dogfood Verification Before fix: $ claw plugins --help Unknown /plugins action '--help'. Use list, install, enable, ... $ claw prompt --help claw v0.1.0 (top-level help, not prompt-specific) After fix: $ claw plugins --help Plugins Usage claw plugins [list|install|enable|disable|uninstall|update] [] Purpose manage bundled and user plugins from the CLI surface ... $ claw prompt --help Prompt Usage claw prompt Purpose run a single-turn, non-interactive prompt and exit Flags --model · --allowedTools · --output-format · --compact ... ## Non-Regression Verification - `claw plugins` (no args) → still displays plugin inventory ✅ - `claw plugins list` → still works correctly ✅ - `claw prompt "text"` → still requires credentials, runs prompt ✅ - All 180 binary tests pass ✅ - All 466 library tests pass ✅ ## Regression Tests Added (4+ assertions) - `plugins --help` → HelpTopic(Plugins) - `prompt --help` → HelpTopic(Prompt) - Short forms `plugins -h` / `prompt -h` both work - `prompt "hello world"` still routes to Prompt action with correct text ## HELP-PARITY SWEEP COMPLETE All 22 top-level subcommands now emit proper help topics: | Command | Status | |---|---| | help --help | ✅ #130e-A | | version --help | ✅ pre-existing | | status --help | ✅ pre-existing | | sandbox --help | ✅ pre-existing | | doctor --help | ✅ pre-existing | | acp --help | ✅ pre-existing | | init --help | ✅ pre-existing | | state --help | ✅ pre-existing | | export --help | ✅ pre-existing | | diff --help | ✅ #130c | | config --help | ✅ #130d | | mcp --help | ✅ pre-existing | | agents --help | ✅ pre-existing | | plugins --help | ✅ #130e-B (this commit) | | skills --help | ✅ pre-existing | | submit --help | ✅ #130e-A | | prompt --help | ✅ #130e-B (this commit) | | resume --help | ✅ #130e-A | | system-prompt --help | ✅ pre-existing | | dump-manifests --help | ✅ pre-existing | | bootstrap-plan --help | ✅ pre-existing | Zero outliers. Contract universally enforced. ## Related - Closes #130e Category B (plugins, prompt surface-parity) - Completes entire help-parity sweep family (#130c, #130d, #130e) - Stacks on #130e-A (dispatch-order fixes) on same worktree --- rust/crates/rusty-claude-cli/src/main.rs | 78 +++++++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index c9bc27d..dff88bc 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -739,6 +739,9 @@ enum LocalHelpTopic { Meta, Submit, Resume, + // #130e-B: help parity — surface-level bugs (plugins, prompt) + Plugins, + Prompt, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -787,20 +790,20 @@ fn parse_args(args: &[String]) -> Result { if !rest.is_empty() && matches!( rest[0].as_str(), - "prompt" - | "commit" + "commit" | "pr" | "issue" ) => { // `--help` following a subcommand that would otherwise forward - // the arg to the API (e.g. `claw prompt --help`) should show - // top-level help instead. Subcommands that consume their own - // args (agents, mcp, plugins, skills) and local help-topic - // subcommands (status, sandbox, doctor, init, state, export, - // version, system-prompt, dump-manifests, bootstrap-plan) must - // NOT be intercepted here — they handle --help in their own - // dispatch paths via parse_local_help_action(). See #141. + // the arg to the API should show top-level help instead. + // Subcommands that consume their own args (agents, mcp, plugins, + // skills) and local help-topic subcommands (status, sandbox, + // doctor, init, state, export, version, system-prompt, + // dump-manifests, bootstrap-plan, diff, config, help, submit, + // resume, prompt) must NOT be intercepted here — they handle + // --help in their own dispatch paths via + // parse_local_help_action(). See #141, #130c, #130d, #130e. wants_help = true; index += 1; } @@ -1286,6 +1289,9 @@ fn parse_local_help_action(rest: &[String]) -> Option> "help" => LocalHelpTopic::Meta, "submit" => LocalHelpTopic::Submit, "resume" => LocalHelpTopic::Resume, + // #130e-B: help parity — surface fixes + "plugins" => LocalHelpTopic::Plugins, + "prompt" => LocalHelpTopic::Prompt, _ => return None, }; Some(Ok(CliAction::HelpTopic(topic))) @@ -6151,6 +6157,23 @@ fn render_help_topic(topic: LocalHelpTopic) -> String { Requires valid Anthropic credentials (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY) Related claw submit · claw --resume · /session list" .to_string(), + // #130e-B: help topic for `claw plugins --help`. + LocalHelpTopic::Plugins => "Plugins + Usage claw plugins [list|install|enable|disable|uninstall|update] [] + Purpose manage bundled and user plugins from the CLI surface + Defaults list (no action prints inventory) + Sources .claw/plugins.json, bundled catalog, user-installed + Formats text (default), json + Related claw mcp · claw skills · /plugins (REPL)" + .to_string(), + // #130e-B: help topic for `claw prompt --help`. + LocalHelpTopic::Prompt => "Prompt + Usage claw prompt + Purpose run a single-turn, non-interactive prompt and exit (like --print mode) + Flags --model · --allowedTools · --output-format · --compact + Requires valid Anthropic credentials (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY) + Related claw submit · claw (bare, interactive REPL)" + .to_string(), } } @@ -10540,6 +10563,43 @@ mod tests { let resume_h = parse_args(&["resume".to_string(), "-h".to_string()]) .expect("resume -h must parse"); assert!(matches!(resume_h, CliAction::HelpTopic(LocalHelpTopic::Resume))); + // #130e-B: surface-level help fixes for plugins and prompt. + // These previously emitted "Unknown action" (plugins) or wrong help (prompt). + let plugins_help = parse_args(&[ + "plugins".to_string(), + "--help".to_string(), + ]) + .expect("plugins --help must parse as help action"); + assert!( + matches!(plugins_help, CliAction::HelpTopic(LocalHelpTopic::Plugins)), + "#130e-B: plugins --help must route to LocalHelpTopic::Plugins, got: {plugins_help:?}" + ); + let prompt_help = parse_args(&[ + "prompt".to_string(), + "--help".to_string(), + ]) + .expect("prompt --help must parse as help action"); + assert!( + matches!(prompt_help, CliAction::HelpTopic(LocalHelpTopic::Prompt)), + "#130e-B: prompt --help must route to LocalHelpTopic::Prompt, got: {prompt_help:?}" + ); + // Short forms + let plugins_h = parse_args(&["plugins".to_string(), "-h".to_string()]) + .expect("plugins -h must parse"); + assert!(matches!(plugins_h, CliAction::HelpTopic(LocalHelpTopic::Plugins))); + let prompt_h = parse_args(&["prompt".to_string(), "-h".to_string()]) + .expect("prompt -h must parse"); + assert!(matches!(prompt_h, CliAction::HelpTopic(LocalHelpTopic::Prompt))); + // Non-regression: `prompt "actual text"` still parses as Prompt action + let prompt_action = parse_args(&[ + "prompt".to_string(), + "hello world".to_string(), + ]) + .expect("prompt with real text must parse"); + assert!( + matches!(prompt_action, CliAction::Prompt { ref prompt, .. } if prompt == "hello world"), + "#130e-B: prompt with real text must route to Prompt action" + ); // #147: empty / whitespace-only positional args must be rejected // with a specific error instead of falling through to the prompt // path (where they surface a misleading "missing Anthropic