Skip to content

Migration: props.FeatureCmdprops.FeatureID

FeatureCmd was named when every feature gated a command. That stopped being true some time ago — telemetry gates a subsystem, and a forge feature gates an initialiser, a config section and an asset bundle — so the Cmd suffix had become actively misleading.

What changed

Before After
props.FeatureCmd props.FeatureID
props.Feature{Cmd: …} props.Feature{ID: …}

Nothing else moved. props.Feature, props.FeatureState, Enable, Disable, SetFeatures, IsEnabled and every feature constant (props.UpdateCmd, props.InitCmd, …) keep their names.

The constants deliberately keep their Cmd suffix

UpdateCmd, InitCmd, McpCmd and friends are unchanged. They are the identifiers every downstream tool passes to Enable/Disable, and renaming them would be a far larger break than the one this buys. Most of them do still name commands.

How to migrate

A mechanical substitution over your Go sources:

grep -rl '\bprops\.FeatureCmd\b' --include='*.go' . \
  | xargs sed -i 's/\bprops\.FeatureCmd\b/props.FeatureID/g'

Then, if you construct props.Feature values directly — most tools do not, they go through Enable/Disable — rename the field:

-props.Feature{Cmd: props.AiCmd, Enabled: true}
+props.Feature{ID: props.AiCmd, Enabled: true}

Your stored configuration and manifests are unaffected

The Feature.ID field is still serialised as cmd:

type Feature struct {
    ID      FeatureID `json:"cmd"     yaml:"cmd"`
    Enabled bool      `json:"enabled" yaml:"enabled"`
}

This is deliberate. Tool.Features is persisted in generator manifests and tool configs. Renaming a Go identifier is a compile-time event you fix once; renaming the wire key would silently stop older manifests loading, with no signal until a regenerate produced the wrong result. So the identifier moved and the tag did not.

There is no config migration to run, and no manifest to rewrite. A TestFeature_WireKeyIsCmd guard in pkg/props pins the wire key, so a later tidy-up cannot quietly change it.

AllFeatures and DefaultFeatures are now functions

Both derive from the feature registry rather than being hand-maintained package variables, so a feature declares its default alongside its identity.

-for _, id := range props.AllFeatures {
+for _, id := range props.AllFeatures() {

DefaultFeatures likewise becomes DefaultFeatures(). Most tools never touch either — they go through SetFeatures, Enable and Disable, which are unchanged.

The practical gain: a feature registered by a blank-imported package now appears in the enumeration. Before this, github and bitbucket were created inline and listed nowhere, so the feature matrix in every doctor support bundle silently omitted both.

Generated projects

The generator now emits props.FeatureID(...) in scaffolded command files. Regenerating an existing project updates its sources along with everything else; a project that is not regenerated keeps compiling against whichever GTB version it pins.