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
53 changes: 35 additions & 18 deletions .github/workflows/no-emdash.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 }}
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down
Loading