diff --git a/.github/workflows/no-emdash.yml b/.github/workflows/no-emdash.yml index 8eaac45..63ed1bd 100644 --- a/.github/workflows/no-emdash.yml +++ b/.github/workflows/no-emdash.yml @@ -1,9 +1,9 @@ name: Em-dash gate # The brand rule (knowledgebase 06-brand/voice-and-tone.md) bans the em dash (U+2014) and the en dash -# (U+2013) across every Cosyte surface, and names commit messages explicitly. The job scans every -# tracked file and its name and, on a pull request, the title, the body and the commit messages. The -# starters themselves are tested by `starters.yml`. +# (U+2013) across every Cosyte surface, and names commit messages explicitly. The `no-emdash` job +# scans every tracked file and its name and, on a pull request, `no-emdash-messages` scans the +# title, the body and the commit messages. The starters themselves are tested by `starters.yml`. # # THE GATE IS NOT IN THIS REPOSITORY. It is the one implementation every Cosyte repository runs, the # `no-emdash` command of `@cosyte/script-utils`, run through npx at the exact version below, since @@ -13,18 +13,22 @@ name: Em-dash gate # # WHY A SEPARATE WORKFLOW, and not a job in `starters.yml`: # -# The deciding factor is the trigger. The PR-text half below needs the `edited` activity +# The deciding factor is the trigger. The messages job below needs the `edited` activity # type, which is not a default: without it, a title or body edited after the last push is # never re-checked, and since this repo squash-merges, the PR title and body ARE the commit # message that lands on main. Re-running every starter on both Node lines each time someone # fixes a typo in a PR description would be a large cost for no extra safety. # -# Both halves live here rather than being split across two workflows: the file scan is a -# checkout, one small download and one scan, so it costs little to re-run on `edited`, and -# one file is one thing to read. +# Both jobs live here rather than in two workflows: the file scan is a checkout, one small +# download and one scan, so it costs little to re-run on `edited`, and one file is one thing +# to read. # -# THE PR-TEXT HALF MUST NEVER BECOME A REQUIRED STATUS CHECK ON ITS OWN. It runs as a step of the -# `no-emdash` job, so the job's single check covers both halves. +# TWO JOBS, AND ONLY ONE IS REQUIRED. The `ci-required-checks` ruleset requires `no-emdash`, the +# tracked-file job, by that name: rename it and every pull request waits on a context nothing +# emits. `no-emdash-messages` MUST NEVER BECOME A REQUIRED CHECK: a Dependabot body quotes upstream +# release notes, dashes included, and a required check would block the bump on text nobody here +# wrote. It still goes red on such a body, so edit the dash out before merging: a squash merge +# makes the body part of the commit on main. on: push: @@ -34,9 +38,10 @@ on: # `edited` is deliberate and load-bearing. See the note above. types: [opened, synchronize, reopened, edited] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +# No concurrency group. A push and a PR edit can arrive together (Dependabot does both), and a run +# a group cancels, even a queued one under `cancel-in-progress: false`, can count as a failed +# `no-emdash` and block the merge until someone re-runs it. Each run takes seconds, so every one +# finishes. permissions: contents: read @@ -51,9 +56,6 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # Full history: the commit-message step below reads the PR's commit range, - # and the default shallow clone does not contain the base commit. - fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 @@ -63,12 +65,27 @@ jobs: - name: Check tracked files and their names run: npx -y "$SCRIPT_UTILS" no-emdash + no-emdash-messages: + # Pull requests only: on a push to main the message is already written, and this job exists + # to stop one landing. + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # Full history: the step below reads the PR's commit range, and the default shallow + # clone does not contain the base commit. + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: "24" + # The rule names commit messages explicitly, and a squash merge composes its message # from the PR title, the PR body, and the branch commits, so all three are checked. - # Pull requests only: on a push to main the message is already written, and this gate - # exists to stop it landing. - name: Check PR title, body, and commit messages - if: github.event_name == 'pull_request' env: PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index d8c803f..e94b3ef 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -10,9 +10,10 @@ on: # `edited` is load-bearing: a title changed after the last push is the title that lands. types: [opened, synchronize, reopened, edited] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +# No concurrency group. A push and a title edit can arrive together (Dependabot does both), and a +# run a group cancels, even a queued one under `cancel-in-progress: false`, can count as a failed +# check and block the merge until someone re-runs it. Each run takes seconds, so every one finishes +# and the newest result stands. permissions: contents: read diff --git a/CLAUDE.md b/CLAUDE.md index 0fffe71..f01930f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,9 +9,10 @@ what CI does with the gate, the hazard, and the rules that bind here. The gate is the four steps in AGENTS.md. Step 1 is `no-emdash.yml`'s `no-emdash` check and `claude-card.yml`, which run `@cosyte/script-utils` through npx at one pinned version; keep no gate -script here. On a pull request `no-emdash` also scans the title, the body, and the commit messages -(its `--stdin` mode), since a squash merge makes them the commit on `main`, and editing the title or -body runs it again. +script here. On a pull request its `no-emdash-messages` job scans the title, the body, and the +commit messages (the `--stdin` mode), since a squash merge makes them the commit on `main`, and +editing the title or body runs it again. That job is never required: a Dependabot body quotes +upstream release notes, dashes and all. Steps 2 and 3 are `starters.yml`: every starter on Node 22 and 24, and `All starters` is the one check that stands for the whole matrix, so a new starter folder is tested with no workflow edit. `starters.yml` also runs nightly on `main`, from the lockfiles. Step 4 is `actionlint.yml`;