diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml new file mode 100644 index 00000000..b5c0d4af --- /dev/null +++ b/.github/workflows/ci-post-merge.yml @@ -0,0 +1,38 @@ +name: CI Post-merge Sentinel + +on: + push: + branches: + - next + - main + +permissions: + contents: read + +concurrency: + group: post-merge-${{ github.ref }} + cancel-in-progress: true + +jobs: + sentinel: + name: Post-merge sentinel (node v22) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup node v22 + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install deps + run: npm ci + + - name: Build project + run: npm run build + + - name: Verify packed binaries + run: npm run verify:packed-binaries diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0d205a4..aefc8b14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,11 @@ name: CI on: - push: - branches: - - main pull_request: - types: + branches: + - next + - main + types: - opened - synchronize - ready_for_review @@ -25,7 +25,7 @@ jobs: strategy: matrix: - node-version: [22, 24] + node-version: [22] steps: - name: Checkout code @@ -49,7 +49,7 @@ jobs: lint: strategy: matrix: - node-version: [22, 24] + node-version: [22] name: Code checks on node v${{ matrix.node-version }} runs-on: ubuntu-latest diff --git a/.github/workflows/promote-next-to-main.yml b/.github/workflows/promote-next-to-main.yml index cf2f2174..80975922 100644 --- a/.github/workflows/promote-next-to-main.yml +++ b/.github/workflows/promote-next-to-main.yml @@ -31,7 +31,24 @@ jobs: - name: Fetch main and next refs run: git fetch origin main next + - name: No-op when next has no commits ahead of main + id: commits-check + shell: bash + run: | + set -euo pipefail + + commit_ahead=$(git rev-list --max-count=1 origin/main..origin/next) + + if [ -z "$commit_ahead" ]; then + echo "No commits to promote (origin/main..origin/next is empty)." + echo "has_commits=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_commits=true" >> "$GITHUB_OUTPUT" + - name: Generate linearis-bot app token + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} id: app-token uses: actions/create-github-app-token@v2 with: @@ -39,6 +56,7 @@ jobs: private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Build PR metadata + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} id: meta env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -112,6 +130,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Upsert promotion PR + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} env: GH_TOKEN: ${{ steps.app-token.outputs.token }} PROMOTION_PR_TITLE: ${{ steps.meta.outputs.title }} diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 8925a6af..fe256f0a 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -4,8 +4,7 @@ on: push: branches: - next - schedule: - - cron: "0 9 * * 1" + - main workflow_dispatch: permissions: @@ -13,7 +12,7 @@ permissions: id-token: write concurrency: - group: release-${{ github.event_name == 'schedule' && 'main' || github.ref_name }} + group: release-${{ github.ref_name }} cancel-in-progress: false jobs: @@ -50,11 +49,7 @@ jobs: id: target shell: bash run: | - if [ "${{ github.event_name }}" = "schedule" ]; then - branch="main" - else - branch="${{ github.ref_name }}" - fi + branch="${{ github.ref_name }}" case "$branch" in main|next) ;; @@ -114,15 +109,6 @@ jobs: - name: Build run: npm run build - - name: Unit tests - run: npm test - - - name: Lint and format check - run: npm run check:ci - - - name: Type check - run: npx tsc --noEmit - - name: Verify npm auth env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36903743..4fbdf995 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,14 +43,15 @@ Integration tests (`tests/integration/`) require `LINEAR_API_TOKEN` in your envi ## Publishing -Publishing is fully automated by the `Release` workflow (`.github/workflows/release-check.yml`). - -- `next` (default branch): releases run on every push (prerelease channel) -- `main` (stable branch): releases run on weekly schedule -- Can be started manually with `workflow_dispatch` for either branch -- Executes build/test/lint/type-check gates before release -- Uses semantic-release to decide if a release is required -- Publishes npm package, creates tag, and creates GitHub release when releasable commits exist +Publishing is automated by GitHub Actions, primarily via the `Release` workflow (`.github/workflows/release-check.yml`). + +At a high level: + +- PR quality gates run in `ci.yml` +- Post-merge sentinel validation runs in `ci-post-merge.yml` +- Releases run in `release-check.yml` (semantic-release decides whether to publish) + +For the authoritative workflow trigger matrix, required check names, and verification commands, see [`docs/ci-run-model.md`](docs/ci-run-model.md) (source of truth). ### Changelog ownership diff --git a/README.md b/README.md index 65987588..f5586e96 100644 --- a/README.md +++ b/README.md @@ -125,14 +125,15 @@ Add this (or a version adapted to your workflow) to your `AGENTS.md` or `CLAUDE. ## Release Automation Policy -Releases are automated by GitHub Actions via `.github/workflows/release-check.yml`. +Linearis uses three CI/release workflows: -- `next` (default branch): release runs on every push (prerelease channel) -- `main` (stable branch): release runs on weekly schedule -- Manual `workflow_dispatch` supports releasing from either `main` or `next` -- `CHANGELOG.md` is automation-owned and must not be edited in pull requests +- `ci.yml` for required pull request checks +- `ci-post-merge.yml` for post-merge sentinel validation on `main`/`next` pushes +- `release-check.yml` for push-driven and manual releases -If a pull request branch contains `CHANGELOG.md` changes anywhere in `main...HEAD` history, CI fails and posts rebase instructions. +For the authoritative trigger matrix, required checks, and operational verification commands, see [`docs/ci-run-model.md`](docs/ci-run-model.md) (source of truth). + +`CHANGELOG.md` is automation-owned and must not be edited in pull requests. If a pull request branch contains `CHANGELOG.md` changes anywhere in `main...HEAD` history, CI fails and posts rebase instructions. ## Contributing diff --git a/docs/ci-run-model.md b/docs/ci-run-model.md new file mode 100644 index 00000000..99702319 --- /dev/null +++ b/docs/ci-run-model.md @@ -0,0 +1,76 @@ +# CI Run Model + +This document defines how pull request checks and release runs are wired together. + +## Workflow purpose + +| Workflow | Purpose | +| --- | --- | +| `ci.yml` | Runs required pull request checks used by branch protection/rulesets. | +| `ci-post-merge.yml` | Runs post-merge sentinel validation on `main`/`next` pushes. | +| `release-check.yml` | Performs semantic-release on release branches after required checks have already passed in PR. | + +## Trigger matrix + +| Event | Branch | `ci.yml` | `ci-post-merge.yml` | `release-check.yml` | +| --- | --- | --- | --- | --- | +| `pull_request` | `main`, `next` | ✅ required checks | ❌ | ❌ | +| `push` | `main`, `next` | ❌ | ✅ post-merge sentinel | ✅ release run | +| `workflow_dispatch` | selected ref (only `main`/`next` accepted by job validation) | ❌ | ❌ | ✅ manual release run (main/next only) | + +> **Constraint:** Although GitHub UI lets you choose any ref for `workflow_dispatch`, `release-check.yml` enforces `main` or `next` only and fails early for other refs. + +## Required checks for repository ruleset + +The repository ruleset must require exactly these check names: + +- Unit tests on node v22 +- Code checks on node v22 + +These checks are produced by `ci.yml` and must be green before merging to `main` or `next`. + +## Release model (no schedule) + +Release workflow is lean and intentionally does not run a weekly schedule: + +- Push to `next` → prerelease channel +- Push to `main` → stable release channel +- Manual `workflow_dispatch` → ad-hoc release from `main` or `next` only (validated in the release job) + +Because release runs happen only after merges, quality gates live in PR required checks, not duplicated release-time full matrices. + +## Operational verification + +Use GitHub CLI to confirm the run model: + +```bash +# CI required checks from PRs + +gh run list --workflow ci.yml --event pull_request --limit 20 + +# Post-merge sentinel runs on push (next) + +gh run list --workflow ci-post-merge.yml --event push --branch next --limit 20 + +# Post-merge sentinel runs on push (main) + +gh run list --workflow ci-post-merge.yml --event push --branch main --limit 20 + +# Release runs on push (main + next) + +gh run list --workflow release-check.yml --event push --limit 20 + +# Optional manual release runs + +gh run list --workflow release-check.yml --event workflow_dispatch --limit 20 +``` + +## Rollback guidance + +If the model needs to be reverted quickly: + +1. Revert the workflow/ruleset refactor commit. +2. Confirm ruleset required checks point to existing check names. +3. Re-run a PR and confirm both required checks appear and pass. +4. Verify push-based release on `next` and `main` with `gh run list` filters above. +5. If release is blocked, run `workflow_dispatch` once as a temporary bridge while fixing ruleset/workflow drift.