fix(cli): list the flags each command registers in --usage

Merged
2Y
7d ago
+310-143

Why

--usage should list exactly the flags each command accepts. Three gaps remained:

  • Whole-body flag name. A body-only operation whose JSON/form body is not expanded into field flags (a union, map, array or scalar body) registers one flag named after the body (--request, --vehicle, …). --usage listed --body instead, which the command rejects with unknown flag: --body, and never listed the real, often required, flag. A multipart body-only operation without flag metadata had the same missing flag and no replacement. --usage used templateHasBodyFlag, while opcmd.go.stmpl decided the registered flag with separate inline logic. In the primary test CLI, 113 commands advertised a --body they reject.
  • completion <shell> --no-descriptions. Cobra registers it on bash/zsh/fish/powershell; the hand-written completion nodes in includes/usage.ts did not list it.
  • Flags added at runtime to a generated command (for example by custom.Register) appeared in --usage only when the command was also marked with usage.MarkDynamic, including on the root.

What changed

  • includes/metadata.ts: new side-effect-free operationUsesMetadataFlags, the request-strategy decision (flag metadata plus optional --body, or one whole-body flag). It replaces the inline logic in opcmd.go.stmpl and intentOperationHasMeta in includes/intents.ts; the now unused operationHasFlagMetadata is removed. Generated command files are byte-identical apart from the [required] change below.
  • includes/usage.ts: getOperationWholeBodyUsageFlag mirrors the flag opcmd.go.stmpl registers: --body with flag metadata; otherwise --<request body name> for body-only operations, with the same help text; otherwise --body. The four completion shell nodes list --no-descriptions.
  • includes/templating.ts, includes/metadata.ts: requestBodyDescription and bodyFlagDescription return unescaped help text for --usage; the template* wrappers escape it for Go source.
  • includes/templating.ts: the whole-body flag help says [required] only when the body is neither optional nor nullable, matching the requiredness the command enforces (templateRequestBodyRequired). This also corrects --help for nullable body-only operations.
  • usage.go.stmpl: every command rendered from its usageNodes entry, marked dynamic or not, takes its flags from cobra (usageFlagLines): generated flag lines for the flags it still shows (a flag hidden at runtime is dropped), then visible local flags added at runtime. MarkDynamic is now only needed when help, aliases or args change at runtime. usageFlagLines reads LocalFlags() first, which merges persistent flags into Flags() before the lookups, so a root marked dynamic before its first render keeps its generated flags.
  • usage_test.go.stmpl:
    • TestUsageSchema_FlagsMatchRegisteredFlags walks every command, root included, and asserts that each flag in its own --usage is registered and each visible registered flag is listed.
    • TestUsageSchema_RuntimeFlagsKeepGeneratedLines replaces TestUsageSchema_DynamicCommandKeepsGeneratedLines and runs with and without MarkDynamic, adding a flag on a generated command and a persistent flag on the root.
    • TestUsageSchema_RuntimeHiddenFlagDropped: a generated flag hidden at runtime leaves the command's --usage.
    • TestUsageSchema_DynamicRoot marks a fresh root dynamic before its first render.
  • pkg/generate/snapshots/cli_empty_body_go_test.go: the generated usageNodes entries of body-only operations list the whole-body flag the command registers (not --body), with [required] exactly when the command requires it, and the completion shell entries list --no-descriptions. These check the table itself; the runtime tests above check the rendered output, which also lists flags missing from the table.
  • README.md: --usage section describes the whole-body flag name, --no-descriptions and runtime-added flags.
  • Review SDK zSDKs/sdk-cli regenerated: chat lists --request instead of --body; completion <shell> lists --no-descriptions. All other --usage output is unchanged.
  • Changeset: core, cli, fix, patch.

Testing

  • TARGET=primary make test-cli: 2189 tests pass.
  • go test ./pkg/generate/snapshots -run TestSnapCLI: pass.
  • make lint: pass.
  • TARGET=review make build-cli: diff limited to internal/usage/schema.go, tests/usage_test.go and gen.lock; generated command files are byte-identical.
  • The new cli_empty_body_go_test.go assertions fail on the base branch templates (standaloneoptional lists --body <body>, not --empty-body) and pass with this change.
  • Against the previous review SDK schema.go, TestUsageSchema_FlagsMatchRegisteredFlags fails (chat --usage lists unregistered --body) and passes with this change.
  • --usage compared for every command path against the base branch:
    • review SDK: 7 of 90 paths differ (root, chat, completion and its four shells), only by the lines above;
    • primary test CLI: 129 of 688 paths differ, only by --body replaced with the registered whole-body flag and --no-descriptions; generated command files differ only where a nullable whole-body flag's help loses [required] (8 commands);
    • a generated CLI with no body-only, non-expanded operations: only completion and its shells differ.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (.claude/skills/public-repo-communication/SKILL.md).
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.