From 764cbdbc7facd001f86c08785337216ad7fe3865 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Tue, 19 May 2026 11:13:13 +0530 Subject: [PATCH] ci: add `npm run gate` local gate + stale-green guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local gate must run EXACTLY what CI runs. Fix agents previously ran `vite build` alone, which skips `scripts/prerender.mjs` — a crashing prerender step shipped a broken main even though the local build passed. - package.json: new `gate` script = `tsc --noEmit && npm run build && vitest run`. `npm run build` already chains `tsc && vite build && node scripts/prerender.mjs` — the exact CI build-and-test sequence. One command an agent/dev runs locally that cannot pass while CI fails. - ci.yml: new `up-to-date-with-base` job fails the PR if its branch does not contain `origin/` as an ancestor (git merge-base --is-ancestor). A stale-but-green PR now goes red, forcing an update-branch before merge instead of shipping a broken base. Verified: `npm run gate` runs clean locally (tsc + build + 662 tests). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ package.json | 1 + 2 files changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3154af4..bc40580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,28 @@ on: branches: [main] jobs: + # Stale-green guard. A PR can show a green CI run that was executed BEFORE a + # breaking commit landed on the base branch — merging it would ship a broken + # main. This job FAILS if the PR branch does not contain origin/ as an + # ancestor, forcing an "Update branch" before the PR can merge. + up-to-date-with-base: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Fail if PR branch is behind its base branch + run: | + BASE="${{ github.event.pull_request.base.ref }}" + git fetch origin "${BASE}" --depth=1 + if git merge-base --is-ancestor "origin/${BASE}" HEAD; then + echo "PR branch contains origin/${BASE} — up to date." + else + echo "::error::PR branch is behind origin/${BASE}. Update the branch (merge/rebase ${BASE}) and re-run CI so it validates against current base." + exit 1 + fi + build-and-test: runs-on: ubuntu-latest steps: @@ -18,6 +40,9 @@ jobs: cache: 'npm' - run: npm ci + # `npm run build` runs `tsc && vite build && node scripts/prerender.mjs`. + # The prerender step is part of CI — a local `vite build` alone is NOT a + # valid gate. `npm run gate` (package.json) runs this exact sequence. - run: npm run build - run: npm test diff --git a/package.json b/package.json index b31343e..858951b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build": "tsc && vite build && node scripts/prerender.mjs", "preview": "vite preview", "test": "vitest run", + "gate": "tsc --noEmit && npm run build && vitest run", "test:watch": "vitest", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui",