CLI variables are global to the invocation, not scoped to a task: `args.Parse` turns every word holding `=` into a global and every other word into a call, so `task build ENV=dev deploy` and `task build deploy ENV=dev` are the same command. The engine assumed the opposite and, as soon as a word matched a task, served only that task's variables — nothing at all when it had none. `task build <TAB>` and even `task build de<TAB>` went silent, where all five legacy wrappers offered task names at every position.
The engine now unions the still-unset requirements of every task named on the line, and falls through to task names once they are all set. The line resolves itself: fill in what blocks execution, then add another task. Keeping the two families exclusive means each keeps a coherent directive, so nothing loses its trailing space.
Task words are matched with FindMatchingTasks instead of a hand-built list of names truncated at their first `*`, which is why `task wildcard-foo <TAB>` used to offer task names rather than the variables of `wildcard-*`. Completion also disables fuzzy matching: a suggestion list has no "did you mean".
Three fixes ride along. `--sort default` left the sorter nil and cleared the one NewExecutor had set, so completion listed tasks in Taskfile order while `--list` sorted them — and a single templated description silently restored the sort through GetTaskList. The bash wrapper never defined KeepOrder, losing the declaration order of `requires`; it now passes `compopt -o nosort`, which bash 3.2 ignores as it already ignores nospace. And the shell suite unsets TASK_EXE and GO_TASK_PROGNAME: fish, Nushell and PowerShell resolve the binary through them, so an ambient value silently tested something other than the binary just built.
`--temp-dir` was missing from the flag-to-directive map, so it fell back to plain file completion while `--dir` and `--remote-cache-dir` offered directories.
The rest is dead weight: `listTasks` re-defaulted a sorter `NewExecutor` already sets and scanned descriptions for templates even with `--no-descriptions`; `detectTaskName` had a `--` branch `Complete` returns before reaching; the two flag-value branches built the same suggestion slice twice. `os.Args[2:]` is now sliced in one place, `complete.Words()`, instead of three, and the test helpers reuse `slicesext.Convert`.
completion/protocol_test.go built a `task` binary in TestMain and drove it through 21 subprocess spawns. Every one of its table cases already had a one-for-one in-process equivalent in internal/complete, and the wire format is asserted by TestWrite_Format.
The one guarantee worth keeping was the `-t -` guard, which stops a keystroke from blocking on a Taskfile read from the terminal. It moves from cmd/task into complete.NeedsTaskfile, where it reads the flagset instead of the flags package global — testable in-process, and one less global read in cmd/task.
TestCompletionShells moves to internal/complete unchanged, and the in-process wildcard fixture gains `matches-exactly-*`, the only pattern of testdata/wildcards it was missing.
The binary path stays covered end to end by completion/tests/run.sh, which the CI completion job runs against five real shells on Linux and macOS.
The `--new-completion` section landed in website/src/latest/, which only release commits write to. Moved verbatim to website/src/next/, where the two files were byte-identical up to that point.
Bash, Fish, Zsh, Nushell and PowerShell now share a single backend: `task __complete` returns the suggestions plus a directive, and every wrapper is a thin shim around it. All five shells offer the same suggestions — task names, aliases, flags, flag values and per-task CLI variables. The Zsh `show-aliases` and `verbose` zstyles keep working, now backed by the `--no-aliases` and `--no-descriptions` completion flags.
The engine is opt-in via `task --new-completion <shell>`, leaving `--completion` and the legacy scripts untouched; it will become the default in a future release. The new wrappers live under `completion/next/`.
Completing a keystroke never reaches the network, never blocks on a stdin entrypoint, and honors every flag that decides how the Taskfile is loaded. Ref resolution shared by `requires` and enum completion moved to `internal/refs`.
A cross-shell test suite exercises the protocol in Go with thin shell smoke tests, and runs in CI.