Skip to content

Physical scale: detect the audited shape (no unit / [1,1,1]) and use the manifest pitch #4518

Physical scale: detect the audited shape (no unit / [1,1,1]) and use the manifest pitch

Physical scale: detect the audited shape (no unit / [1,1,1]) and use the manifest pitch #4518

name: Large PR review gate
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
pull_request_review:
types: [submitted, edited, dismissed]
permissions:
contents: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
gate:
name: Gate
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Block large PRs unless approved
uses: actions/github-script@v7
env:
LARGE_PR_FILE_THRESHOLD: "20"
with:
script: |
const threshold = Number(process.env.LARGE_PR_FILE_THRESHOLD || "20");
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request?.number ?? context.issue.number;
if (!prNumber) {
core.setFailed("Could not determine PR number from event payload.");
return;
}
const prResp = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber,
});
const pr = prResp.data;
const changedFiles = pr.changed_files ?? 0;
const author = pr.user?.login;
core.info(`PR #${prNumber}: changed_files=${changedFiles}, threshold=${threshold}, author=${author}`);
if (pr.draft) {
core.info("Draft PR detected — skipping gate.");
return;
}
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
// Sort defensively so "last review wins" is stable even if API ordering changes.
reviews.sort((a, b) => {
const aTime = new Date(a.submitted_at ?? 0).getTime();
const bTime = new Date(b.submitted_at ?? 0).getTime();
if (aTime !== bTime) return aTime - bTime;
return (a.id ?? 0) - (b.id ?? 0);
});
const latestStateByUser = new Map();
const decisionStates = new Set(["APPROVED", "CHANGES_REQUESTED", "DISMISSED"]);
for (const r of reviews) {
const login = r.user?.login;
if (!login) continue;
if (login === author) continue;
if (!decisionStates.has(r.state)) continue;
latestStateByUser.set(login, r.state);
}
let approvals = 0;
for (const state of latestStateByUser.values()) {
if (state === "APPROVED") approvals += 1;
}
core.info(`Approvals (excluding author): ${approvals}`);
if (changedFiles > threshold && approvals < 1) {
core.setFailed(
`This PR changes ${changedFiles} files (> ${threshold}). Add at least 1 approving review to merge.`
);
} else {
core.info("Gate passed.");
}