name: Detect affected areas description: >- Classify a PR's changed files into CI work categories (python, frontend, site) so heavy jobs can skip work they cannot be affected by. Outputs are always "true" on push/dispatch events and fail open (everything "true") when the diff cannot be computed — a skipped category must never be a false negative. # The caller must check out the repo with `fetch-depth: 0` BEFORE using this # action, so both the PR base and head commits are present for `git diff`. outputs: python: description: Run Python tests / ruff / ty / windows-footguns. value: ${{ steps.classify.outputs.python }} frontend: description: Run the TypeScript typecheck matrix + desktop build. value: ${{ steps.classify.outputs.frontend }} site: description: Build the Docusaurus docs site. value: ${{ steps.classify.outputs.site }} runs: using: composite steps: - name: Classify changed files id: classify shell: bash env: EVENT_NAME: ${{ github.event_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | set -euo pipefail # Only pull_request events are gated. Other events (push, release, # dispatch) leave CHANGED empty, so the classifier fails open and every # lane runs — post-merge / on-demand validation is never weakened. if [ "$EVENT_NAME" = "pull_request" ]; then # Three-dot diff = what the PR introduces vs its merge base, matching # how a reviewer reads it. An uncomputable diff (shallow clone, etc.) # yields an empty list, which the classifier also fails open on. CHANGED="$(git diff --name-only "${BASE_SHA}...${HEAD_SHA}" || true)" fi echo "Changed files:" printf '%s\n' "${CHANGED:-(none)}" # Caller already checked out the repo, so the classifier is at its # repo-relative path. It is the single source of the fail-open default. printf '%s\n' "${CHANGED:-}" | python3 scripts/ci/classify_changes.py