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 ophis library and are unchanged.
See spec 0191.