Migrating to the command-group model¶
A command that exists only to group its subcommands has nothing to implement. It
now gets no Run<Name> at all, and its generated cmd.go wires the framework
default instead:
Invoked bare it prints its usage and exits 0. Given a verb it does not have it
says so and exits 2:
$ tool alpha
Usage:
tool alpha [command]
...
$ tool alpha bogus
WARN unknown command "bogus" for "tool alpha": unknown subcommand
$ echo $?
2
Implements spec 0190.
Why this changed¶
The generator used to write a Run<Name> returning
errorhandling.ErrRunSubCommand for every command with children, and wire RunE
to call it, so a bare group exited 2.
Two problems followed from cmd.go referring to a function in the developer's own
package:
- Where
.gtb/ignoresealed amain.gothat was absent, the generator could neither create the callee nor stub it, and the emitted call did not compile (undefined: RunAlpha). - The fix for that suppressed the
RunEin exactly that case, which meant the exit code of a built binary depended on the ignore file. The same group exited0or2according to whether a seal happened to cover a file that was not there.
setup.GroupRunE refers to nothing in your package, so neither can happen. A seal
now governs what the generator writes and nothing about what your tool does.
What changes for your project¶
Run gtb regenerate project. The run tells you which commands changed:
1 command changed behaviour
WARN group no longer returns a usage error command=alpha
path=pkg/cmd/alpha/cmd.go was="exit 2 (subcommand required)"
now="exit 0 (usage), exit 2 on an unknown subcommand"
keep-old="give RunAlpha a body returning errorhandling.ErrRunSubCommand"
It reports on the run that makes the change, and stays silent afterwards.
A group whose Run<Name> is an untouched stub¶
Nothing to do. The generator stops calling that stub and wires the default. Your
main.go is not edited or deleted. The stub stays exactly as it is, and it is a
live seam rather than litter: give it a body and the next run classifies the group
as working again.
A group with a hand-written Run<Name>¶
Nothing changes. A body is intent, and the generator keeps calling it. That includes a parent whose body only prints its own verb list. The generator cannot tell that apart from real work without guessing, so it does not try.
If such a group should take the new behaviour, delete the function body and
regenerate. You lose a hand-maintained verb list that drifts from the real
subcommand set every time a child is added, and gain the unknown-verb report.
regenerate names the candidates for you, once, on the run that changes another
group.
A group that wants a bare invocation to stay an error¶
Give its Run<Name> a body returning the sentinel, which is still available:
func RunAlpha(ctx context.Context, props *props.Props, opts *AlphaOptions, args []string) error {
return errorhandling.ErrRunSubCommand
}
That makes it a working group, so the generator wires RunE to it as before.
A main.go that now defines nothing¶
A pure group with no pre-run hooks has nothing to put in main.go, so a newly
scaffolded one gets none. Existing files are left alone.
What did not change¶
- Leaves. A command with no children still gets a
Run<Name>returningerrorhandling.ErrNotImplemented, and still exits2. - The root command. Cobra already reports an unknown command there, and exits
1. That inconsistency with a group's2is left as it is. The root is cobra's to answer. errorhandling.ErrRunSubCommand. Still exported, still correct for a hand-written command that wants a bare invocation to be a usage error. GTB simply stops generating it.
Version skew¶
Emitted code references setup.GroupRunE and, through it,
errorhandling.ErrUnknownSubCommand, added in go/errorhandling v0.4.0. A
project regenerated by a newer gtb while pinned to an older go-tool-base will
not compile.
Bump the framework, then regenerate. If go.mod is in your .gtb/ignore,
gtb cannot bump the pin for you and the mismatch arrives as a build error
naming the missing symbol.
The gtb CLI itself¶
gtb's own groups adopted the same model, so gtb <group> <unrecognised> was
silent help and exit 0, and is now a named error and exit 2, gtb generate,
remove, regenerate, ignore, attach, detach, template, telemetry and
keys. A script relying on the old status was relying on a bug: the command it
asked for never ran.
gtb mcp and its children are built by the go/mcp module and are unchanged.
See spec 0191.