diff --git a/.commitlintrc.json b/.commitlintrc.json index 40f9842..d68dd54 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -9,7 +9,7 @@ "scope-enum": [ 2, "always", - ["repo", "nvim", "wezterm", "kitty", "tmux", "clrs", "swatch", "scripts", "shared", "global"] + ["repo", "nvim", "wezterm", "tmux", "clrs", "swatch", "scripts", "shared", "global"] ], "scope-empty": [2, "never"], "subject-empty": [2, "never"], diff --git a/.github/workflows/commit-checks.yml b/.github/workflows/commit-checks.yml new file mode 100644 index 0000000..5467cbf --- /dev/null +++ b/.github/workflows/commit-checks.yml @@ -0,0 +1,72 @@ +name: Commit checks + +on: + pull_request: + branches: + - main + types: + - edited + - opened + - ready_for_review + - reopened + - synchronize + +permissions: + contents: read + +concurrency: + group: commit-checks-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + pull-request-author: + name: Validate pull request author + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + timeout-minutes: 1 + + steps: + - name: Require the repository owner + env: + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + REPOSITORY_OWNER: ${{ github.repository_owner }} + run: | + if [[ "$PR_AUTHOR" != "$REPOSITORY_OWNER" ]]; then + printf 'Pull requests must be created by %s, received %s\n' "$REPOSITORY_OWNER" "$PR_AUTHOR" + exit 1 + fi + + pull-request-title: + name: Validate pull request title + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + timeout-minutes: 5 + + env: + HUSKY: 0 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.29.3 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Validate pull request title + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: printf '%s\n' "$PR_TITLE" | pnpm exec commitlint --verbose diff --git a/.github/workflows/main-history-audit.yml b/.github/workflows/main-history-audit.yml new file mode 100644 index 0000000..e9a494d --- /dev/null +++ b/.github/workflows/main-history-audit.yml @@ -0,0 +1,59 @@ +name: Main history audit + +on: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: main-history-audit-${{ github.ref }} + cancel-in-progress: false + +jobs: + commit-messages: + name: Audit main commit messages + runs-on: ubuntu-latest + timeout-minutes: 5 + + env: + HUSKY: 0 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.29.3 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Audit pushed commit messages + env: + AFTER_SHA: ${{ github.event.after }} + BEFORE_SHA: ${{ github.event.before }} + run: | + if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then + commit_range="$AFTER_SHA" + else + commit_range="$BEFORE_SHA..$AFTER_SHA" + fi + + while IFS= read -r commit; do + git log -1 --format=%B "$commit" | pnpm exec commitlint --verbose + done < <(git rev-list --reverse "$commit_range") diff --git a/AGENTS.md b/AGENTS.md index 627b96b..7b3febb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,5 +115,8 @@ YAML files are formatted with Prettier and staged Lua files with StyLua. - Husky `commit-msg` runs `pnpm exec commitlint --edit "$1"`. - Commit messages must use conventional types from `.commitlintrc.json` and a - non-empty scope. Allowed scopes are `repo`, `nvim`, `wezterm`, `kitty`, `tmux`, + non-empty scope. Allowed scopes are `repo`, `nvim`, `wezterm`, `tmux`, `clrs`, `swatch`, `scripts`, `shared`, and `global`. +- Pull request titles follow the same rules and are the source of squash commit + titles. Pull requests must come from the personal repository owner, and GitHub + audits every commit added to `main` after merging. diff --git a/README.md b/README.md index 5a8b6d9..42ec103 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,18 @@ applies safe lint and formatting changes and then runs `check`. - Vitest for tests - Oxlint, Prettier, and StyLua for code quality - Husky, lint-staged, and Commitlint for commit checks + +## Contributions + +Changes enter `main` through pull requests and squash merging. Pull request +titles use the Conventional Commit rules in `.commitlintrc.json`; GitHub checks +the title again whenever it changes. In this personal repository, the pull +request author must also be the repository owner. Development branch commits +may be temporary because only the squashed pull request is part of the main +history. + +Configure GitHub to use the pull request title as the default squash commit +title. After each push to `main`, the history audit validates every new commit +message as a final safeguard. The audit is intentionally post-merge and cannot +undo an invalid commit, so the pull request title is the required pre-merge +contract.