I'm not "a developer" so I never got the use case of tools like these. Instead I just use the stuff they mention (asdf, make).
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...
All the features are opt-in. I started using mise because I wanted something like asdf only without the bad UX, and mise can use asdf plugins.
For env vars, you don't need to load them into your shell if you don't want to. When you run a task, mise will make sure the env vars in your config are set, so thats not something you need to worry about.
I still use shell scripts like you describe, mise just supercharges them a bit. When I need to make sure my teammates have the tools in that script (like jq) installed, mise just ensure they are installed before running the command, as long as you declare them in your tools list.
If your setup works for you thats great.
if you use asdf you can drop mise right in and it'll work the same but faster and with better supply-chain security. people have been doing this for almost 2 years and mise fits that use-case perfectly.
You don't have to touch the env vars and tasks stuff.
I think you should give `mise` a chance. I believe it can help improve your workflow.
It's better at managing tools than `asdf`, very close to `direnv` and superior to `make` as a task runner (more verbose but much easier to understand). One of the advantages is that `mise` tasks can be standalone files (you can even write file tasks in python if you prefer, see https://mise.jdx.dev/tasks/file-tasks.html)
i have similar tree-containing-symlinks-to-one-thing - and i do it by symlinking each x to ../x ; and only the root x (of any tree) is a real thing (or missing, if it lives on some other device). Thus the structure is still tar/archievable.
Of course you can do things like these too:
$ MAKEFLAGS="-f /that/root/makefile" make
or (rude)
$ alias make="make -f /that/rooty/makefile"
but beware that adding another -f somemakefile will load both specified.
Apart of it, my biggest grievance with make is that it cannot handle spaces in names. By design.
I use Mise as a drop-in replacement for asdf. It's fully backwards compatible with .tool-versions and other config files, and unlike asdf it uses a PATH-based approach instead of shims.