Compare commits

..

5 Commits

Author SHA1 Message Date
YeonGyu-Kim
3a533ceba0 fix(#152-follow-up-2): claw bootstrap-plan rejects trailing arguments
## What Was Broken

`claw bootstrap-plan garbage` silently accepted the `garbage` argument:

    $ claw bootstrap-plan garbage
    - CliEntry
    - FastPathVersion
    - StartupProfiler
    - ...

Same pattern as #152 init follow-up (previous cycle): a no-arg diagnostic
verb was missing its `rest.len()` guard.

## What This Fix Does

Add parse-time length check before constructing CliAction::BootstrapPlan:

    "bootstrap-plan" => {
        if rest.len() > 1 {
            return Err(format!(
                "unrecognized argument \`{}\` for subcommand \`bootstrap-plan\`",
                rest[1]
            ));
        }
        Ok(CliAction::BootstrapPlan { output_format })
    }

## Dogfood Verification

Before:
    $ claw bootstrap-plan garbage
    - CliEntry
    - FastPathVersion
    (continues listing...)

After:
    $ claw bootstrap-plan garbage
    [error-kind: cli_parse]
    error: unrecognized argument `garbage` for subcommand `bootstrap-plan`

    $ claw bootstrap-plan  (no args)
    - CliEntry
    - FastPathVersion
    (still works normally)

## Full No-Arg Verb Suffix-Guard Sweep (Post-Fix)

All no-arg verbs now uniformly reject trailing garbage:

| Verb | Status |
|---|---|
| help garbage |  rejects |
| version garbage |  rejects |
| status garbage |  rejects |
| sandbox garbage |  rejects |
| doctor garbage |  rejects |
| state garbage |  rejects |
| init garbage |  rejects (previous cycle #55) |
| diff garbage |  rejects |
| plugins garbage |  rejects |
| skills garbage |  rejects |
| system-prompt garbage |  rejects |
| dump-manifests garbage |  rejects |
| bootstrap-plan garbage |  rejects (this commit) |
| acp garbage |  rejects |

Legitimate positionals (not a bug):
- `export <file-path>` — file path is the intended arg
- `config <section>` — flexible section filter (design question, not bug)

## Non-Regression

- `claw bootstrap-plan` (no args) still works 
- All 180 binary tests pass 
- All 466 library tests pass 

## Related

- #152 follow-up (init suffix guard, previous cycle)
- Completes diagnostic-verb suffix-guard contract hygiene
2026-04-23 02:17:16 +09:00
YeonGyu-Kim
860f285f70 fix(#152-follow-up): claw init rejects trailing arguments
## What Was Broken

`claw init foo` silently accepted the `foo` argument instead of rejecting it at parse time:

    $ claw init foo
    Init
      Project          /path/to/project
      .claw/           skipped

Trailing args should be rejected like other no-arg verbs (status, sandbox, etc.):

    $ claw status bar
    [error-kind: cli_parse]
    error: unrecognized argument `bar` for subcommand `status`

## Root Cause

The `init` arm (main.rs:1191) was missing a `rest.len()` guard.
Compare to `status` (main.rs:1319 in parse_diagnostic_verb), which correctly
rejects extra args.

## What This Fix Does

Add a simple length guard before constructing the CliAction::Init:

    "init" => {
        if rest.len() > 1 {
            return Err(format!(
                "unrecognized argument \`{}\` for subcommand \`init\`",
                rest[1]
            ));
        }
        Ok(CliAction::Init { output_format })
    }

## Dogfood Verification

Before:
    $ claw init foo
    Init
      Project          /path/to/project

After:
    $ claw init foo
    [error-kind: cli_parse]
    error: unrecognized argument `foo` for subcommand `init`

    $ claw init
    Init
      Project          /path/to/project
      (continues normally)

## Non-Regression

- `claw init` (no args) still works
- All 180 binary tests pass
- All 466 library tests pass

## Related

- #152 (diagnostic verb suffix guard)
- Follows pattern from parse_diagnostic_verb() guard clauses
2026-04-23 02:14:48 +09:00
YeonGyu-Kim
9dd7e79eb2 fix(#130e-B): route plugins/prompt --help to dedicated help topics
## 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] [<target>]
      Purpose          manage bundled and user plugins from the CLI surface
      ...

    $ claw prompt --help
    Prompt
      Usage            claw prompt <prompt-text>
      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
2026-04-23 02:07:50 +09:00
YeonGyu-Kim
0ca034472b fix(#130e-A): route help/submit/resume --help to help topics before credential check
## What Was Broken (ROADMAP #130e, filed cycle #53)

Three subcommands leaked `missing_credentials` errors when called
with `--help`:

    $ claw help --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

    $ claw submit --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

    $ claw resume --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

This is the same dispatch-order bug class as #251 (session verbs).
The parser fell through to the credential check before help-flag
resolution ran. Critical discoverability gap: users couldn't learn
what these commands do without valid credentials.

## Root Cause (Traced)

`parse_local_help_action()` (main.rs:1260) is called early in
`parse_args()` (main.rs:1002), BEFORE credential check. But the
match statement inside only recognized:
status, sandbox, doctor, acp, init, state, export, version,
system-prompt, dump-manifests, bootstrap-plan, diff, config.

`help`, `submit`, `resume` were NOT in the list, so the function
returned `None`, and parsing continued to credential check which
then failed.

## What This Fix Does

Same pattern as #130c (diff) and #130d (config):

1. **LocalHelpTopic enum extended** with Meta, Submit, Resume variants
2. **parse_local_help_action() extended** to map the three new cases
3. **Help topic renderers added** with accurate usage info

Three-line change to parse_local_help_action:

    "help" => LocalHelpTopic::Meta,
    "submit" => LocalHelpTopic::Submit,
    "resume" => LocalHelpTopic::Resume,

Dispatch order (parse_args):
    1. --resume parsing
    2. parse_local_help_action() ← NOW catches help/submit/resume --help
    3. parse_single_word_command_alias()
    4. parse_subcommand() ← Credential check happens here

## Dogfood Verification

Before fix (all three):
    $ claw help --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

After fix:
    $ claw help --help
    Help
      Usage            claw help [--output-format <format>]
      Purpose          show the full CLI help text (all subcommands, flags, environment)
      ...

    $ claw submit --help
    Submit
      Usage            claw submit [--session <id|latest>] <prompt-text>
      Purpose          send a prompt to an existing managed session
      Requires         valid Anthropic credentials (when actually submitting)
      ...

    $ claw resume --help
    Resume
      Usage            claw resume [<session-id|latest>]
      Purpose          restart an interactive REPL attached to a managed session
      ...

## Non-Regression Verification

- `claw help` (no --help) → still shows full CLI help 
- `claw submit "text"` (with prompt) → still requires credentials 
- `claw resume` (bare) → still emits slash command guidance 
- All 180 binary tests pass 
- All 466 library tests pass 

## Regression Tests Added (6 assertions)

- `help --help` → routes to HelpTopic(Meta)
- `submit --help` → routes to HelpTopic(Submit)
- `resume --help` → routes to HelpTopic(Resume)
- Short forms: `help -h`, `submit -h`, `resume -h` all work

## Pattern Note

This is Category A of #130e (dispatch-order bugs). Same class as #251.
Category B (surface-parity: plugins, prompt) will be handled in a
follow-up commit/branch.

## Help-Parity Sweep Status

After cycle #52 (#130c diff, #130d config), help sweep revealed:

| Command | Before | After This Commit |
|---|---|---|
| help --help | missing_credentials |  Meta help |
| submit --help | missing_credentials |  Submit help |
| resume --help | missing_credentials |  Resume help |
| plugins --help | "Unknown action" |  #130e-B (next) |
| prompt --help | wrong help |  #130e-B (next) |

## Related

- Closes #130e Category A (dispatch-order help fixes)
- Same bug class as #251 (session verbs)
- Stacks on #130d (config help) on same worktree branch
- #130e Category B (plugins, prompt) queued for follow-up
2026-04-23 02:03:10 +09:00
YeonGyu-Kim
19638a015e fix(#130d): accept --help / -h in claw config arm, route to help topic
## What Was Broken (ROADMAP #130d, filed cycle #52)

`claw config --help` was silently ignored — the command executed and
displayed the config dump instead of showing help:

    $ claw config --help
    Config
      Working directory /private/tmp/dogfood-probe-47
      Loaded files      0
      Merged keys       0
      (displays full config, not help)

Expected: help for the config command. Actual: silent acceptance of
`--help`, runs config display anyway.

This is the opposite outlier from #130c (which rejected help with an
error). Together they form the help-parity anomaly:
- #130c `diff --help` → error (rejects help)
- #130d `config --help` → silent ignore (runs command, ignores help)
- Others (status, mcp, export) → proper help
- Expected behavior: all commands should show help on `--help`

## Root Cause (Traced)

At main.rs:1050, the `"config"` parser arm parsed arguments positionally:

    "config" => {
        let tail = &rest[1..];
        let section = tail.first().cloned();
        // ... ignores unrecognized args like --help silently
        Ok(CliAction::Config { section, ... })
    }

Unlike the `diff` arm (#130c), `config` had no explicit check for
extra args. It positionally parsed the first arg as an optional
`section` and silently accepted/ignored any trailing arg, including
`--help`.

## What This Fix Does

Same pattern as #130c (help-surface parity):

1. **LocalHelpTopic enum extended** with new `Config` variant
2. **parse_local_help_action() extended** to map `"config"` → `LocalHelpTopic::Config`
3. **config arm guard added**: check for help flag before parsing section
4. **Help topic renderer added**: human-readable help text for config

Fix locus at main.rs:1050:

    "config" => {
        // #130d: accept --help / -h and route to help topic
        if rest.len() >= 2 && is_help_flag(&rest[1]) {
            return Ok(CliAction::HelpTopic(LocalHelpTopic::Config));
        }
        let tail = &rest[1..];
        // ... existing parsing continues
    }

## Dogfood Verification

Before fix:
    $ claw config --help
    Config
      Working directory ...
      Loaded files      0
      (no help, runs config)

After fix:
    $ claw config --help
    Config
      Usage            claw config [--cwd <path>] [--output-format <format>]
      Purpose          merge and display the resolved configuration
      Options          --cwd overrides the workspace directory
      Output           loaded files and merged key-value pairs
      Formats          text (default), json
      Related          claw status · claw doctor · claw init

Short form `claw config -h` also works.

## Non-Regression Verification

- `claw config` (no args) → still displays config dump 
- `claw config permissions` (section arg) → still works 
- All 180 binary tests pass 
- All 466 library tests pass 

## Regression Tests Added (4 assertions)

- `config --help` → routes to `HelpTopic(LocalHelpTopic::Config)`
- `config -h` (short form) → routes to help topic
- bare `config` (no args) → still routes to `Config` action
- `config permissions` (with section) → still works correctly

## Pattern Note

#130c and #130d form a pair: two outlier failure modes in help
handling for local introspection commands:
- #130c `diff` rejected help (loud error) → fixed with guard + routing
- #130d `config` silently ignored help (silent accept) → fixed with same pattern

Both are now consistent with the rest of the CLI (status, mcp, export, etc.).

## Related

- Closes #130d (config help discoverability gap)
- Completes help-parity family (#130c, #130d)
- Stacks on #130c (diff help fix) on same worktree branch
- Part of help-consistency thread (#141 audit)
2026-04-23 01:55:25 +09:00

View File

@@ -733,6 +733,15 @@ enum LocalHelpTopic {
BootstrapPlan,
// #130c: help parity for `claw diff --help`
Diff,
// #130d: help parity for `claw config --help`
Config,
// #130e: help parity — dispatch-order bugs (help, submit, resume)
Meta,
Submit,
Resume,
// #130e-B: help parity — surface-level bugs (plugins, prompt)
Plugins,
Prompt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -781,20 +790,20 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
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;
}
@@ -1006,7 +1015,18 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
match rest[0].as_str() {
"dump-manifests" => parse_dump_manifests_args(&rest[1..], output_format),
"bootstrap-plan" => Ok(CliAction::BootstrapPlan { output_format }),
"bootstrap-plan" => {
// #152: bootstrap-plan is a no-arg verb. Reject unexpected suffixes
// like `claw bootstrap-plan garbage` that silently accept trailing
// args instead of rejecting at parse time.
if rest.len() > 1 {
return Err(format!(
"unrecognized argument `{}` for subcommand `bootstrap-plan`",
rest[1]
));
}
Ok(CliAction::BootstrapPlan { output_format })
}
"agents" => Ok(CliAction::Agents {
args: join_optional_args(&rest[1..]),
output_format,
@@ -1046,6 +1066,10 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
// which is synthetic friction. Accepts an optional section name
// (env|hooks|model|plugins) matching the slash command shape.
"config" => {
// #130d: accept --help / -h and route to help topic instead of silently ignoring
if rest.len() >= 2 && is_help_flag(&rest[1]) {
return Ok(CliAction::HelpTopic(LocalHelpTopic::Config));
}
let tail = &rest[1..];
let section = tail.first().cloned();
if tail.len() > 1 {
@@ -1175,7 +1199,16 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
"system-prompt" => parse_system_prompt_args(&rest[1..], output_format),
"acp" => parse_acp_args(&rest[1..], output_format),
"login" | "logout" => Err(removed_auth_surface_error(rest[0].as_str())),
"init" => Ok(CliAction::Init { output_format }),
"init" => {
// #152: init is a no-arg verb. Reject unexpected suffixes like `claw init foo`.
if rest.len() > 1 {
return Err(format!(
"unrecognized argument `{}` for subcommand `init`",
rest[1]
));
}
Ok(CliAction::Init { output_format })
}
"export" => parse_export_args(&rest[1..], output_format),
"prompt" => {
let prompt = rest[1..].join(" ");
@@ -1270,6 +1303,15 @@ fn parse_local_help_action(rest: &[String]) -> Option<Result<CliAction, String>>
"bootstrap-plan" => LocalHelpTopic::BootstrapPlan,
// #130c: help parity for `claw diff --help`
"diff" => LocalHelpTopic::Diff,
// #130d: help parity for `claw config --help`
"config" => LocalHelpTopic::Config,
// #130e: help parity — dispatch-order fixes
"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)))
@@ -6102,6 +6144,56 @@ fn render_help_topic(topic: LocalHelpTopic) -> String {
Formats text (default), json
Related claw status · claw config"
.to_string(),
// #130d: help topic for `claw config --help`.
LocalHelpTopic::Config => "Config
Usage claw config [--cwd <path>] [--output-format <format>]
Purpose merge and display the resolved .claw.json / settings.json configuration
Options --cwd overrides the workspace directory for config lookup
Output loaded files and merged key-value pairs (text) or JSON object (json)
Formats text (default), json
Related claw status · claw doctor · claw init"
.to_string(),
// #130e: help topic for `claw help --help` (meta-help).
LocalHelpTopic::Meta => "Help
Usage claw help [--output-format <format>]
Purpose show the full CLI help text (all subcommands, flags, environment)
Aliases claw --help · claw -h
Formats text (default), json
Related claw <subcommand> --help · claw version"
.to_string(),
// #130e: help topic for `claw submit --help`.
LocalHelpTopic::Submit => "Submit
Usage claw submit [--session <id|latest>] <prompt-text>
Purpose send a prompt to an existing managed session without starting a new one
Defaults --session latest (resumes the most recent managed session)
Requires valid Anthropic credentials (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY)
Related claw prompt · claw --resume · /session list"
.to_string(),
// #130e: help topic for `claw resume --help`.
LocalHelpTopic::Resume => "Resume
Usage claw resume [<session-id|latest>]
Purpose restart an interactive REPL attached to a managed session
Defaults latest session if no argument provided
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] [<target>]
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 <prompt-text>
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(),
}
}
@@ -10412,6 +10504,122 @@ mod tests {
diff_bad_arg.contains("unexpected extra arguments"),
"#130c: diff with unknown arg must still error, got: {diff_bad_arg}"
);
// #130d: `claw config --help` must route to help topic, not silently run config.
let config_help_action = parse_args(&[
"config".to_string(),
"--help".to_string(),
])
.expect("config --help must parse as help action");
assert!(
matches!(config_help_action, CliAction::HelpTopic(LocalHelpTopic::Config)),
"#130d: config --help must route to LocalHelpTopic::Config, got: {config_help_action:?}"
);
let config_h_action = parse_args(&[
"config".to_string(),
"-h".to_string(),
])
.expect("config -h must parse as help action");
assert!(
matches!(config_h_action, CliAction::HelpTopic(LocalHelpTopic::Config)),
"#130d: config -h (short form) must route to LocalHelpTopic::Config"
);
// #130d: bare `claw config` still routes to Config action with no section
let config_action = parse_args(&[
"config".to_string(),
])
.expect("bare config must parse as config action");
assert!(
matches!(config_action, CliAction::Config { section: None, .. }),
"#130d: bare config must still route to Config action with section=None"
);
// #130d: config with section still works (non-regression)
let config_section = parse_args(&[
"config".to_string(),
"permissions".to_string(),
])
.expect("config permissions must parse");
assert!(
matches!(config_section, CliAction::Config { section: Some(ref s), .. } if s == "permissions"),
"#130d: config with section must still work"
);
// #130e: dispatch-order help fixes for help, submit, resume
// These previously emitted `missing_credentials` instead of showing help,
// because parse_local_help_action() didn't route them. Now they route
// to dedicated help topics before credential check.
let help_help = parse_args(&[
"help".to_string(),
"--help".to_string(),
])
.expect("help --help must parse as help action");
assert!(
matches!(help_help, CliAction::HelpTopic(LocalHelpTopic::Meta)),
"#130e: help --help must route to LocalHelpTopic::Meta, got: {help_help:?}"
);
let submit_help = parse_args(&[
"submit".to_string(),
"--help".to_string(),
])
.expect("submit --help must parse as help action");
assert!(
matches!(submit_help, CliAction::HelpTopic(LocalHelpTopic::Submit)),
"#130e: submit --help must route to LocalHelpTopic::Submit"
);
let resume_help = parse_args(&[
"resume".to_string(),
"--help".to_string(),
])
.expect("resume --help must parse as help action");
assert!(
matches!(resume_help, CliAction::HelpTopic(LocalHelpTopic::Resume)),
"#130e: resume --help must route to LocalHelpTopic::Resume"
);
// Short form `-h` works for all three
let help_h = parse_args(&["help".to_string(), "-h".to_string()])
.expect("help -h must parse");
assert!(matches!(help_h, CliAction::HelpTopic(LocalHelpTopic::Meta)));
let submit_h = parse_args(&["submit".to_string(), "-h".to_string()])
.expect("submit -h must parse");
assert!(matches!(submit_h, CliAction::HelpTopic(LocalHelpTopic::Submit)));
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