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
This commit is contained in:
YeonGyu-Kim 2026-04-23 02:17:16 +09:00
parent 860f285f70
commit 3a533ceba0

View File

@ -1015,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,