From 706ff72db152a01194729b6caa1d3675f0e78f9a Mon Sep 17 00:00:00 2001 From: Aric Camarata Date: Tue, 1 Sep 2026 16:44:21 -0400 Subject: [PATCH] ci: consolidate trivial gates into hygiene.yml, add concurrency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidation: generated-file-gate, multi-arch-check, and doc-sync (each a few seconds, no toolchain) were three separate near-instant workflows each claiming their own runner on every push/PR. Merged into one hygiene.yml job with a single checkout — 1 runner claim instead of 3. Trigger scope is the union of the three originals, so nothing that used to run stops running. The doc-sync steps are guarded to pull_request events only (the script reads github.base_ref, unset on push). clean-root.yml and gitleaks.yml are DELIBERATELY LEFT ALONE, even though clean-root is exactly the class of job this consolidation targets. Both back required branch-protection checks ('clean-root' and 'gitleaks') and already carry their own concurrency groups — the safest option per the task brief is to leave two checks branch protection depends on for every PR as untouched, standalone workflow files rather than risk a check-name change from folding them into a shared file. Verified byte-identical to origin/main. nself-first-check.yml, security.yml, desktop-{linux,macos,windows}.yml, mobile-ci.yml, tvos-ci.yml, and e2e.yml are untouched — all already carry concurrency and tight paths: filters (docker-compose paths, mobile/**, tv/**, tests/**, etc.); nothing to add. ci.yml is untouched — already has concurrency, and 4 of its jobs back required checks ('nself CLI Check', 'Version Lockstep', 'Mobile Tests (coverage gate >=60%)', 'I18n Completeness Gate (N-S3-T3)'). No paths filter was added: GitHub does not skip a required check just because paths-ignore excluded a push, it blocks merge waiting on a check that never runs, so path-filtering a workflow that carries ANY required check is unsafe. Concurrency added to wiki-sync.yml and dependabot-review.yml (both plain push/pull_request, not pull_request_target, so ref-keyed grouping is safe and a stale rerun losing to a fresher one is the correct behavior). Left alone, all release/publish/schedule-triggered by design: android-release.yml, ios-release.yml, publish-updater.yml, quarterly-doc-audit.yml (per the deploy-workflow exception), and desktop-release.yml (already cancel-in-progress: false, correctly, and untouched). dependabot-auto-merge.yml untouched (pull_request_target trap). Required check names 'clean-root' (job id + name in clean-root.yml) and 'gitleaks' (job id in gitleaks.yml) are unchanged — files are byte-identical to origin/main. ci.yml is also byte-identical to origin/main, preserving 'nself CLI Check', 'Version Lockstep', 'Mobile Tests (coverage gate >=60%)', and 'I18n Completeness Gate (N-S3-T3)'. --- .github/workflows/dependabot-review.yml | 4 + .github/workflows/generated-file-gate.yml | 59 ----------- .../workflows/{doc-sync.yml => hygiene.yml} | 98 +++++++++++++++++-- .github/workflows/multi-arch-check.yml | 28 ------ .github/workflows/wiki-sync.yml | 4 + 5 files changed, 96 insertions(+), 97 deletions(-) delete mode 100644 .github/workflows/generated-file-gate.yml rename .github/workflows/{doc-sync.yml => hygiene.yml} (60%) delete mode 100644 .github/workflows/multi-arch-check.yml diff --git a/.github/workflows/dependabot-review.yml b/.github/workflows/dependabot-review.yml index c36dfa9..887d2aa 100644 --- a/.github/workflows/dependabot-review.yml +++ b/.github/workflows/dependabot-review.yml @@ -21,6 +21,10 @@ permissions: contents: read pull-requests: write +concurrency: + group: dependabot-review-${{ github.ref }} + cancel-in-progress: true + jobs: dependabot-review: name: Dependabot PR Gate diff --git a/.github/workflows/generated-file-gate.yml b/.github/workflows/generated-file-gate.yml deleted file mode 100644 index 0be61ed..0000000 --- a/.github/workflows/generated-file-gate.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Generated File Gate - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - generated-file-gate: - name: Block committed generated files - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Scan diff for GENERATED BY markers - shell: bash - run: | - set -euo pipefail - - if [ "${{ github.event_name }}" = "pull_request" ]; then - git fetch origin main --depth=1 - BASE="origin/main" - else - BASE="${{ github.event.before }}" - if [ -z "$BASE" ] || ! git cat-file -e "$BASE" 2>/dev/null; then - BASE="HEAD~1" - fi - fi - - CHANGED=$(git diff --name-only "$BASE"...HEAD || true) - if [ -z "$CHANGED" ]; then - echo "No changed files to scan." - exit 0 - fi - - FAIL=0 - while IFS= read -r f; do - [ -f "$f" ] || continue - if head -n 5 "$f" | grep -q "# GENERATED BY"; then - if git check-ignore -q "$f"; then - echo "OK: $f is generated but gitignored." - else - echo "ERROR: Refusing to commit generated file. Add to .gitignore first." - echo " -> $f" - FAIL=1 - fi - fi - done <<< "$CHANGED" - - if [ "$FAIL" -eq 1 ]; then - echo "ERROR: Refusing to commit generated file. Add to .gitignore first." - exit 1 - fi - - echo "Generated File Gate: clean." diff --git a/.github/workflows/doc-sync.yml b/.github/workflows/hygiene.yml similarity index 60% rename from .github/workflows/doc-sync.yml rename to .github/workflows/hygiene.yml index 955f378..63bc09b 100644 --- a/.github/workflows/doc-sync.yml +++ b/.github/workflows/hygiene.yml @@ -1,27 +1,104 @@ -name: Doc-Sync Ritual - -# Enforces the Doc-Sync Ritual (P93 Theme 27). -# Doctrine: https://github.com/nself-org/ — ~/Sites/nself/.claude/docs/doctrines/doc-sync-ritual.md -# Blocks any PR that changes a user-visible surface without matching doc updates. +name: Hygiene + +# Consolidates the trivial, no-toolchain repo gates into one workflow with a +# single checkout, so a push doesn't queue three separate near-instant jobs +# for three separate runners. +# +# clean-root.yml and gitleaks.yml are DELIBERATELY NOT folded in here, even +# though clean-root is exactly this class of job. Both are required +# branch-protection checks ("clean-root" and "gitleaks") and GitHub matches +# a required check by job name/id — moving either job into a shared +# workflow file is a legitimate way to preserve the name, but the safer +# choice for two checks branch protection depends on every PR is to leave +# them as their own standalone workflow files untouched. Neither is touched +# by this change. +# +# Trigger scope is the union of the three originals — generated-file-gate +# and multi-arch-check ran on push/pull_request[main] only (multi-arch-check +# also allowed workflow_dispatch), doc-sync ran on pull_request +# [opened, synchronize, reopened] with no branch restriction (the bare +# `pull_request:` default below covers the same three types). Union is +# broader, never narrower, so nothing that used to run stops running. The +# doc-sync steps are guarded to PR events only (the script reads +# github.base_ref, which push events don't set). +# +# nself-first-check.yml is deliberately NOT folded in — it's already scoped +# to `paths: ['**/docker-compose*.yml', ...]` and fires on almost no +# pushes. Merging it into a workflow that must run on every push would make +# it run far more often, which is the opposite of the goal. on: + push: + branches: [main] pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: hygiene-${{ github.ref }} cancel-in-progress: true + jobs: - doc-sync: + hygiene: + name: hygiene runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 + # ── from generated-file-gate.yml ───────────────────────────── + - name: Scan diff for GENERATED BY markers + shell: bash + run: | + set -euo pipefail + + if [ "${{ github.event_name }}" = "pull_request" ]; then + git fetch origin main --depth=1 + BASE="origin/main" + else + BASE="${{ github.event.before }}" + if [ -z "$BASE" ] || ! git cat-file -e "$BASE" 2>/dev/null; then + BASE="HEAD~1" + fi + fi + + CHANGED=$(git diff --name-only "$BASE"...HEAD || true) + if [ -z "$CHANGED" ]; then + echo "No changed files to scan." + exit 0 + fi + + FAIL=0 + while IFS= read -r f; do + [ -f "$f" ] || continue + if head -n 5 "$f" | grep -q "# GENERATED BY"; then + if git check-ignore -q "$f"; then + echo "OK: $f is generated but gitignored." + else + echo "ERROR: Refusing to commit generated file. Add to .gitignore first." + echo " -> $f" + FAIL=1 + fi + fi + done <<< "$CHANGED" + + if [ "$FAIL" -eq 1 ]; then + echo "ERROR: Refusing to commit generated file. Add to .gitignore first." + exit 1 + fi + + echo "Generated File Gate: clean." + + # ── from multi-arch-check.yml ──────────────────────────────── + - name: Multi-arch check (N/A) + run: | + echo "Repo type: TypeScript app surfaces (RN/Expo, Vite, Tauri, rn-tvos) + nSelf backend" + echo "No Go binaries or Docker images produced. Multi-arch check N/A. PASS." + + # ── from doc-sync.yml — PR-only, needs github.base_ref ─────── - name: Compute PR diff id: diff + if: github.event_name == 'pull_request' run: | git fetch origin "${{ github.base_ref }}" --depth=1 changed=$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD || true) @@ -31,6 +108,7 @@ jobs: echo "$changed" - name: Doc-Sync matrix check + if: github.event_name == 'pull_request' env: CHANGED: ${{ steps.diff.outputs.changed }} run: | diff --git a/.github/workflows/multi-arch-check.yml b/.github/workflows/multi-arch-check.yml deleted file mode 100644 index 65c3c87..0000000 --- a/.github/workflows/multi-arch-check.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Multi-Arch Check - -# Asserts multi-architecture build compatibility. -# Repo type: TypeScript app surfaces + nSelf backend (nself-org/ntask) -# Authority: S30-T05, STORM-CROSS-PLATFORM -# Template: .claude/docs/doctrines/multi-arch-check.workflow.yml - -on: - pull_request: - branches: [main] - push: - branches: [main] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - multi-arch-stub: - name: Multi-arch check (N/A) - runs-on: ubuntu-latest - - steps: - - name: Report repo type - run: | - echo "Repo type: TypeScript app surfaces (RN/Expo, Vite, Tauri, rn-tvos) + nSelf backend" - echo "No Go binaries or Docker images produced. Multi-arch check N/A. PASS." - diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 7d888cf..15714fb 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -9,6 +9,10 @@ on: - '.github/workflows/wiki-sync.yml' workflow_dispatch: +concurrency: + group: wiki-sync-${{ github.ref }} + cancel-in-progress: true + jobs: sync-wiki: runs-on: ubuntu-latest