For example… let’s say you have just plain search-replace and no smart tools. You need to update github.com/abc/def to github.com/abc/def/v2. This is a search-replace operation.
This only happens when packages publish breaking changes. The minor version is stored in go.mod.
Every language starts the way you describe, then needs are added.
What is complex versioning? In Go, you specify a minimum minor version, and breaking changes change the package name & import path (or they’re supposed to). Why would I want complex versioning? The underlying problem that versioning solves is complicated, but that doesn’t mean you benefit from a complex versioning scheme.
gofmt -w -r '"github.com/abc/def" -> "github.com/abc/def/v2"' .
That won't do subpackages though.