Repository navigation
Conversation
Root and group `--usage` were served from KDL strings rendered at generation time, one full string per command path. Commands attached after generation (`custom: true` placeholders filled in by `custom.Register`, leaves mounted under generated groups, flags added to generated commands) were missing from every ancestor's schema or appeared as bare nodes, while their own `--usage` was correct. A command marked dynamic was rendered live from cobra, which dropped `suggestions`, `default=`, declared flag order and placeholders, and took arg help/requiredness from the prompt annotation. The generator now emits `usageNodes`: for each command path, its head line, aliases, args, flags (keyed by long name, with settings/hidden markers) and declared child names, rendered once at indent 0. `EmitSchema` composes every schema from the assembled cobra tree: - generated nodes render verbatim; children keep declared order, children removed at runtime are dropped, available children added at runtime are appended; - a command marked with `usage.MarkDynamic`, or absent from the table, takes `help` from the runtime `Short`, keeps the generated alias/arg/flag lines for everything it still has (flags hidden at runtime are dropped) and renders only runtime-added aliases and local flags live; - settings flags stay on a command's own schema only, as before. Commands unchanged at runtime render byte-identically to the previous output. Each node is stored once instead of being repeated in every ancestor's string, roughly halving the generated schema source. Tests: `TestUsageSchema_ComposesRuntimeCommands`, `TestUsageSchema_DynamicCommandKeepsGeneratedLines` and `TestUsageSchema_DynamicRootUnchanged`; the empty-body snapshot test follows the new table shape. README `--usage` section updated; review SDK regenerated.
- Live-rendered aliases, args and flags take the node's item prefix, so a dynamic root lists runtime root flags at indent 0 instead of 2. - A generated child hidden at runtime is dropped from its parent's schema, matching children added at runtime, which already required an available command. - The root node is a parentless command with subcommands; any other parentless command renders itself from cobra instead of the root schema. Tests: `TestUsageSchema_DynamicRoot` (replaces `TestUsageSchema_DynamicRootUnchanged` and adds the runtime root flag), `TestUsageSchema_HiddenGeneratedCommandDropped`, `TestUsageSchema_DetachedDynamicCommand`. Review SDK regenerated.
There was a problem hiding this comment.
All reported issues were addressed across 9 files
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
A parentless command group was treated as the CLI root because root detection only checked `Parent() == nil && HasSubCommands()`, so `EmitSchema` on a detached group printed the generated root's metadata and dropped the group's own `cmd` node. Only commands whose `Root()` is the generated CLI root (`usageRootName`) now use the generated `usageNodes` table; a detached command and its children render from cobra. README: state precisely what a dynamic command keeps (generated arg lines; alias and flag lines it still has; generated `hide=#true` flags) and that detached commands render entirely from cobra. `TestUsageSchema_DetachedCommand` replaces `TestUsageSchema_DetachedDynamicCommand` and adds a detached group whose child shares a generated command's name. Review SDK regenerated.
A dynamic command reused every generated arg line whenever the node had
args, and every generated flag line whose long name still existed. A
same-named replacement registered with `cobra.NoArgs` and
`BoolP("id", "x", ...)` therefore still advertised `arg "id"` and
`-i --id <id>`, in its own schema and in every ancestor's.
`usageNodes` now records, per generated flag, its shorthand and whether it
takes an operand or repeats, and each arg line carries its name. For a
dynamic command:
- generated arg lines are kept only while the runtime
`speakeasy_prompt_args` names match them; otherwise args render live;
- a generated flag line is kept only while cobra's flag has the same
shorthand, operand and repeatability; otherwise that flag renders live in
its generated position.
Generated `default=` values are not compared: they are the documented
effective defaults, which intents may apply outside cobra, so cobra's
`DefValue` differs for unchanged flags.
Tests: `TestUsageSchema_ReplacedCommandDropsStaleFlagSyntax` (own and root
schema), `TestUsageSchema_ReplacedIntentDropsStaleArgs`;
`TestUsageSchema_DynamicRoot` now asserts the exact root output after a
runtime root flag is added. README and review SDK updated.
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
`MarkDynamic` merges a command's generated lines with its runtime state, and nothing else rendered fully live, so a same-named replacement of a generated command could still advertise generated `default=`, `suggestions`, `env` and help text it no longer has. Cobra metadata cannot tell an unchanged command from a replacement: generated intents register flags with zero-value defaults while the table documents the effective default the intent applies itself. Use command identity instead. `NewRootCommand` calls `usage.MarkGenerated` on the tree right before `custom.Register`, and on cobra's `help` and `completion` once they are created. Only marked commands use their `usageNodes` entry; anything attached later, including a same-named replacement, renders live from cobra while keeping the node's declared child order. `MarkDynamic` keeps its role for porcelain that extends a generated command in place. This supersedes the per-flag shorthand/operand/repeat signature: pflag cannot remove a flag, so on a marked command every table flag is the generated flag object. The `short`/`value`/`repeat` table fields and `usageFlagUnchanged` are removed. Tests: `TestUsageSchema_ReplacedCommandRendersLive` (exact own and root block for an unmarked replacement), `TestUsageSchema_GeneratedCommandsMarked`; `TestUsageSchema_ReplacedIntentDropsStaleArgs` no longer marks the replacement dynamic. README and review SDK updated.
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
…e validation docs - `TestUsageSchema_ReplacedIntentDropsStaleArgs` now asserts the exact own schema of the replacement and that root `--usage` renders it as a bare node at its depth, without the generated `arg` lines. - README: under `--usage`, required-flag validation cannot fail (`output.InstallErrorHandling` clears the required-flag annotations via `usage.DisarmRequiredFlags` and re-arms them after `RunE`); only Cobra's flag-group validators can still win. Review SDK regenerated.
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would require human review. Refactors generated CLI --usage to compose schemas from the runtime tree so runtime-added commands and flags appear, with tests; it changes the public --usage contract and makes compatibility tradeoffs requiring human sign-off.
View guided diff | Turn on auto-fix | Re-trigger cubic
…-runtime-commands # Conflicts: # zSDKs/sdk-cli/.speakeasy/gen.lock
…-runtime-commands
…-runtime-commands
…ets in --usage - `NewRootCommand` marked every child named `help` or `completion` as generated after `custom.Register`. Cobra keeps an existing `completion` command, so a custom one was rendered from the built-in table entry and lost its help and flags. `usage.InitDefaultCommands` now wraps `InitDefaultHelpCmd`/`InitDefaultCompletionCmd` and marks only the commands cobra created. - The generated mark now records the identity of the command's flag sets. A marked command whose flags are replaced with `ResetFlags` renders live instead of reusing generated flag lines for flags it no longer has. - `MarkDynamic`'s doc comment describes the merge it performs. Tests: `TestUsageSchema_CustomCompletionRendersLive`, `TestUsageSchema_ResetFlagsRendersLive`. README and review SDK updated.
There was a problem hiding this comment.
0 issues found across 8 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would require human review. Refactors generated CLI --usage to compose schemas from the runtime tree so runtime-added commands and flags appear; this deliberately changes a machine-readable public contract, and the dismissed hidden-flag fix is not visible in the truncated diff.
View guided diff | Turn on auto-fix | Re-trigger cubic
Why
Generated CLIs serve
--usage(the KDL machine schema) from strings rendered at generation time, one full string per command path. Anything attached to the command tree after generation is invisible to every ancestor's schema:custom: truecommands filled in bycustom.Registerappear in root--usageas barecmd "<name>" help="…"nodes with no args or flags;custom.Registermounts under a generated group are missing from root--usageand from the group's--usage;--usage.Each such command's own
--usagewas correct, because a command marked withusage.MarkDynamicis rendered live from cobra. That live rendering was itself lossy next to the generated text: nosuggestions, nodefault=, flags in alphabetical rather than declared order, generic<flag_name>placeholders, and arg help/requiredness taken from the prompt annotation instead of the generated node.Tools and agents that discover a CLI through root
--usagetherefore miss hand-written commands and flags entirely.What changed
templates/cli/includes/usage.ts:templateUsageSchemaMap(one complete KDL string per path, each ancestor repeating its whole subtree, plus dead alias-path keys) is replaced bytemplateUsageNodeMap. It emitsusageNodes: for each command path, the node's head line, aliases, args, flags (keyed by long name, withsettings/hiddenmarkers) and declared child names, each rendered once at indent 0.templates/cli/usage.go.stmpl:EmitSchemacomposes every schema, root and subtrees alike, from the assembled cobra tree (renderUsageNode):NewRootCommandcallsusage.MarkGeneratedon the tree right beforecustom.Register, andusage.InitDefaultCommandsmarks only thehelp/completioncommands cobra itself creates (a customcompletionstays live). The mark records the command's flag-set identity. Only marked commands with unchanged flag sets use their table node; anything attached later, including a same-named replacement, and a command whose flags were replaced withResetFlags, renders live from cobra. Cobra metadata cannot tell an unchanged command from a replacement (generated intents register zero-value cobra defaults while the table documents the effective default), so identity is the signal;usage.MarkDynamic(porcelain extending it in place) takeshelpfrom its runtimeShort, keeps generated arg lines while thespeakeasy_prompt_argsnames match, keeps generated alias and flag lines for those it still has (flags generated visible but hidden at runtime are dropped), and renders runtime-added aliases and visible local flags live, so generatedsuggestions,default=and declared order survive;internal/usage/schema.gois roughly half its previous size.Usage; commands added in the rootcustom-commandsregion run before the mark and count as generated.templates/cli/usage_test.go.stmpl: new tests for runtime commands composed into ancestors (with byte-identity of the rest of the root), a dynamic command with runtime flags (own schema and root), a dynamic root (unchanged output; exact output after a runtime root flag), a generated command hidden at runtime, a detached leaf and group, same-named replacements with and withoutMarkDynamic(exact own and root blocks), a replaced intent withcobra.NoArgs, the generated mark on built-in and cobra commands, a customcompletionregistered before cobra's defaults, and a marked command afterResetFlags.pkg/generate/snapshots/cli_empty_body_go_test.go: theschema.gopattern follows theusageNodesentry shape.templates/cli/README.md:--usagesection describes the composition rules.zSDKs/sdk-cli:internal/usage/schema.goandtests/usage_test.goregenerated. Root--usagenow lists the--greetingflag thatcustom.Registerattaches tohello; every other path is unchanged.core, cli, fix, patch.Testing
TARGET=primary make test-cli: 2185 tests pass (after merging currentmain).go test ./pkg/generate/snapshots -run TestSnapCLI: pass.make lint: pass.TARGET=review make build-cli: diff limited tointernal/usage/schema.go,tests/usage_test.goandgen.lock.schema.goand pass with this change.--usageoutput compared for every command path:--greetingflag;custom.Registeradds top-level commands, group leaves and flags on generated commands: unchanged commands are byte-identical; root and the affected groups gain the runtime commands and flags; dynamic commands keep their generatedsuggestions,default=and declared order.helpvalues after re-indentation.Public-safety check
.agents/skills/public-repo-communication/SKILL.md).git diff --check.