I use Asdf to manage versions of all programs in a monorepo. Works great (well, actually asdf's UX is terrible, but it works reliably, and the plugin design is great).
For development, I don't ever load environment variables into my current shell session. I run a script or Makefile which loads any necessary variables, does a thing, and then exits. It would be a nightmare to have to constantly check if my current shell session had X variable in it.
I use Make for repeatable small commands that will vary per directory, or for simple parallelizing or ordered execution of commands. I have a big one that handles Helm installs, and a few more for Terraform, Packer, asdf, etc. I also use them for deployments in hierarchical environment directories, where environment variables are loaded from parent directories. I love that Make has all the features it has, because I always find myself eventually reaching for something you don't find in "just a task runner", and it makes my life easier.
I use shell scripts when I need to make a composeable tool that'll be slightly longer or more complicated than a Make target should be. I have saved so much time and effort writing these tools in shell rather than Python or something, where there is inevitably way more bugs and dependencies. The only time I have needed to use something more complex than shell is when I have a lot of APIs to deal with that also deal in JSON; if it's a lot of complexity it's better than curl/jq, but if it's only one small task, curl/jq is better.
The end result works great. The whole environment just needs asdf installed (from Homebrew, for example). With stock Make and the stock Bash v3, I can manage everything automatically, everything's version-pinned and automated, all variables get loaded at runtime as needed, and the whole thing can be grokked by just reading some simple Makefiles.
The only thing I want to fix now is to get rid of the superfluous Makefiles from directories (they're all symlinked back to one Makefile). It's a pain to re-symlink them all when I change directory structure. Probably should just write a script for it...