Computing an affected set from the diff

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.