Path filters answer whether a workflow runs; deciding which packages a change affects needs the dependency graph as well as the diff.
changed=$(git diff --name-only "$BASE"..."$HEAD"
| awk -F/ '/^packages//{print $2}' | sort -u)
# and the closure over dependents, which is the half that
# path filters cannot do:
affected=$(./bin/dependents $changed | sort -u)
echo "matrix=$(jq -cn --args '.positional' $affected)"
>> "$GITHUB_OUTPUT"
The transitive closure is what makes this correct rather than merely faster — a change to a shared package must test every package that depends on it, and a naive path filter tests only the one that changed. The failure mode is a build that passes because it skipped the thing that broke, which is worse than a slow pipeline and is invisible until a deploy.