Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 47 additions & 69 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,15 @@ jobs:
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
run: node scripts/ci-change-scope.mjs
run: |
if [ "${{ github.event.inputs.refresh_lighthouse_baseline }}" = "true" ]; then
# A baseline refresh is a focused measurement operation, not a
# synthetic full repository change. Keep the trusted workflow/perf
# contracts without starting unrelated build, UI, DB or container jobs.
node scripts/ci-change-scope.mjs --files .github/actions/setup-lighthouse-chromium/action.yml
else
node scripts/ci-change-scope.mjs
fi

sync-pr-policy-body:
name: Sync PR policy body
Expand Down Expand Up @@ -209,7 +217,8 @@ jobs:
run: npm run check:pr-mergeability

- name: Focused CI workflow contracts
if: needs.changes.outputs.workflow_changed == 'true'
# Full coverage already contains these workflow-reading Vitest files.
if: needs.changes.outputs.workflow_changed == 'true' && needs.changes.outputs.coverage_changed != 'true'
run: npm run test:ci-workflows

- name: Codex auto-resolve workflow guard
Expand Down Expand Up @@ -247,7 +256,7 @@ jobs:
run: npm run format:changed

- name: Scheduled full-tree format drift
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.refresh_lighthouse_baseline != 'true')
run: npm run format:check

# Design-system guards that were previously only in the local verify:cheap
Expand Down Expand Up @@ -481,36 +490,8 @@ jobs:
uses: ./.github/actions/setup-ui-e2e

- name: Chromium @critical journeys
env:
PLAYWRIGHT_BUILD_ROOT_ID: ci-production
PLAYWRIGHT_KEEP_BUILD_ROOT: "true"
run: npm run test:e2e:critical

# Run-scoped only: publish the webpack filesystem cache for the dependent
# production shards in this workflow run. Do not use actions/cache — the
# ~804 MB warm cache was measured as a net CI loss against the shared
# 10 GB budget (would evict the Playwright browser cache). Artifacts expire
# after one day and never compete with that budget.
# continue-on-error: publish is an optimization. A miss must not fail the
# critical job after green @critical journeys — shards cold-build instead.
- name: Publish isolated Next.js build cache
if: success()
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-next-build-cache-${{ github.run_id }}
# Path lives under `.next-playwright/` (dot-directory). upload-artifact
# v4.4+ excludes hidden paths unless this is set — without it the
# publish is a silent no-op and every shard cold-builds.
path: .next-playwright/ci-production/dist/cache
include-hidden-files: true
# Next's production webpack cache is already uncompressed; leave
# compression to the outer transport at level 0 so the critical path
# does not pay zlib-6 CPU on ~800 MB.
compression-level: 0
retention-days: 1
if-no-files-found: error

- name: Classify exact failed test identities
if: failure()
run: node scripts/classify-playwright-failures.mjs
Expand Down Expand Up @@ -556,12 +537,11 @@ jobs:
fail-fast: false
matrix:
# THREE explicit duration-aware file groups (scripts/playwright-pr-shards.mjs),
# not Playwright `--shard=i/N`. Count-balanced `--shard` packed the slow
# phone-scroll family into one runner (measured 9m36 vs 6m54/6m20 on
# CI 30530618838). Explicit groups mix slow-per-test specs with faster
# mega-specs. `tests/playwright-pr-shards.test.ts` fails closed if any
# production e2e:pr file is missing, duplicated, or leaves a shard empty.
# Re-measure wall time after suite growth before reshuffling membership:
# not Playwright `--shard=i/N`. The first explicit split still measured
# 5.5m / 3.7m / 2.4m of tests on CI 31285952061; the current profiles use
# those per-file durations and keep both full and post-critical totals
# within 30 seconds. Contract tests fail closed on membership or balance
# drift. Re-measure after suite growth before reshuffling membership:
# node scripts/playwright-pr-shards.mjs --list
# node scripts/playwright-pr-shards.mjs --validate
shard: [1, 2, 3]
Expand All @@ -574,24 +554,15 @@ jobs:
- name: Setup UI e2e environment
uses: ./.github/actions/setup-ui-e2e

# ui-critical-fast publishes a run-scoped artifact (not actions/cache) so
# all three shards can reuse webpack state without competing for the
# shared 10 GB cache budget. Server processes and reports stay isolated.
# continue-on-error: a missing artifact must not fail the shard — cold build
# remains the correct fallback when critical skipped or published nothing.
- name: Restore isolated Next.js build cache
if: needs.ui-critical-fast.result == 'success'
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: playwright-next-build-cache-${{ github.run_id }}
path: .next-playwright/ci-production/dist/cache

- name: Chromium production journeys
env:
PLAYWRIGHT_BUILD_ROOT_ID: ci-production
PLAYWRIGHT_KEEP_BUILD_ROOT: "true"
run: npm run test:e2e:pr:shard -- --shard ${{ matrix.shard }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "merge_group" ]; then
npm run test:e2e:pr:shard -- --shard ${{ matrix.shard }} --exclude-critical
else
# The fast subset is skipped on main/schedule/manual runs, so these
# events retain the complete production set.
npm run test:e2e:pr:shard -- --shard ${{ matrix.shard }}
fi

- name: Classify exact failed test identities
if: failure()
Expand Down Expand Up @@ -1124,7 +1095,19 @@ jobs:
release-browser-matrix:
if: >
always() &&
(github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) &&
(
(github.event_name == 'workflow_dispatch' && github.event.inputs.refresh_lighthouse_baseline != 'true') ||
github.event_name == 'schedule' ||
startsWith(github.ref, 'refs/heads/release/') ||
(
github.ref == 'refs/heads/main' &&
(
needs.changes.outputs.ui_changed == 'true' ||
needs.changes.outputs.perf_changed == 'true' ||
needs.changes.outputs.lockfile_changed == 'true'
Comment thread
cursor[bot] marked this conversation as resolved.
)
)
) &&
needs.changes.result == 'success' &&
needs.static-pr.result == 'success' &&
(needs.build.result == 'success' || needs.build.result == 'skipped') &&
Expand All @@ -1142,19 +1125,6 @@ jobs:
- name: Setup Node and dependencies
uses: ./.github/actions/setup-node-cached

- name: Restore Next.js build cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: .next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('.nvmrc', 'package-lock.json') }}-${{ hashFiles('src/**', 'data/**', 'public/**', 'next.config.ts', 'tsconfig.json', 'postcss.config.mjs') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('.nvmrc', 'package-lock.json') }}-

- name: Build
env:
NEXT_BUILD_CPUS: "4"
run: npm run build

- name: Restore browser cache
id: pw-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
Expand All @@ -1177,7 +1147,15 @@ jobs:

- name: Full browser UI matrix
id: e2e-matrix
run: npm run test:e2e
run: |
if [ "${{ needs.changes.outputs.ui_changed }}" = "true" ] && [ "${{ needs.ui-critical.result }}" = "success" ]; then
# Production Chromium already passed in this run. Keep mockup
# Chromium plus the cross-browser backstop without repeating it.
npm run test:e2e -- --project=chromium-mockups --project=firefox --project=webkit
else
# Fail-safe path for perf/lockfile-only or otherwise skipped UI proof.
npm run test:e2e
fi

- name: Upload UI diagnostics
if: failure()
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ still free to change, and it stops the blanket default from being applied silent

- Do not run a broad baseline routinely before localized work, and do not select `verify:cheap` merely because a change is described as “non-trivial.” Use `npm run verify:cheap` once when cross-module risk warrants a broad offline gate. Use `npm run verify:pr-local` when a change is ready for PR handoff: it now classifies the changed paths, runs focused documentation/workflow contracts for recognised low-risk scopes, and fails closed to lint, typecheck, the full unit suite, RAG fixture validation, and relevant build/domain gates for executable or unknown scope. If the diff has not changed, do not run `verify:cheap` first merely to repeat the same coverage.
- Do not stack focused tests, full tests, typecheck, lint, build, and browser checks unless each catches a distinct plausible regression. Do not rerun an unchanged successful gate. A deliberately skipped low-yield broad gate is not automatically verification debt; report the skipped check and its risk-based reason concisely.
- A fast-fail subset may precede a broader required gate only when the later gate excludes that subset for the same event; retain a fail-safe full path whenever the subset is skipped. Likewise, do not pre-run a build, install, or server setup that the selected wrapper performs itself. Guard these disjoint/fallback rules with workflow contract tests so a later edit cannot silently restore duplicate work or create a coverage hole.
- Use dry-run selectors before expensive gates when scope is uncertain. `npm run verify:pr-local -- --dry-run --files <comma-separated paths>` inspects PR-local selection without running commands. The broader `--extended` plan is dry-run only unless explicit approval is reflected by `ALLOW_EXTENDED_PR_LOCAL=true`.
- CI uses the same fail-closed scope model: recognised docs and workflow/policy-only changes run focused contracts; executable product/test/config, dependency, database, container, RAG, security-sensitive, mixed, or unknown paths retain the applicable heavy jobs. Do not broaden a path trigger or restore an always-on heavy job without evidence that the focused route misses a realistic failure class. Scheduled drift/release checks and the always-reporting `PR required` aggregate remain safety backstops.
- Let the repository run coordinator control cross-worktree verification. It permits at most two focused Vitest/read-only typecheck leases from different worktrees; full Vitest, coverage, lint, build, Playwright, and live-provider tests remain exclusive. Do not install while a repository test, build, lint, typecheck, or server command is active. Avoid aggressive short-interval polling, and do not repeat an unchanged full gate after it passes.
Expand Down
Loading
Loading