From 01eb3f961c0c8ae459f34ae07e85ba14aff2ede0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 07:53:08 +0530 Subject: [PATCH] ci(coverage): gate PRs on 100% patch coverage + 95% project floor Adds diff-cover patch-coverage enforcement to the coverage workflow: every changed line in a PR must be covered by a test (--fail-under=100), and the total project coverage must stay >=95%. Go coverage is converted to Cobertura via gocover-cobertura so diff-cover can read it. Gated to pull_request events (push has no base_ref). New permanent org mandate. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/coverage.yml | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d4f657f..7ac6d10 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,6 +17,9 @@ jobs: - uses: actions/checkout@v4 with: path: common + # Full history so diff-cover can resolve origin/ for the + # patch-coverage gate below (shallow clones lack the base commit). + fetch-depth: 0 - uses: actions/checkout@v4 with: repository: InstaNode-dev/proto @@ -34,3 +37,39 @@ jobs: files: common/coverage.out flags: common fail_ci_if_error: false + + # ------------------------------------------------------------------ + # Org patch-coverage mandate: every changed line in a PR diff must be + # covered by a test (100%), and the project floor stays >=95%. + # Tool: diff-cover (https://github.com/Bachmann1234/diff-cover) reads a + # Cobertura report + the git diff vs the base branch. + # ------------------------------------------------------------------ + - uses: actions/setup-python@v5 + if: github.event_name == 'pull_request' + with: + python-version: '3.12' + - name: Install diff-cover + cobertura converter + if: github.event_name == 'pull_request' + run: | + pip install diff-cover + go install github.com/boumenot/gocover-cobertura@latest + - name: Convert coverage to Cobertura + if: github.event_name == 'pull_request' + working-directory: common + run: $(go env GOPATH)/bin/gocover-cobertura < coverage.out > coverage.xml + - name: Patch coverage gate (100% of changed lines) + if: github.event_name == 'pull_request' + working-directory: common + run: | + git fetch origin "${{ github.base_ref }}" --depth=1 || true + diff-cover coverage.xml \ + --compare-branch="origin/${{ github.base_ref }}" \ + --fail-under=100 + - name: Project coverage floor (>=95% total) + if: github.event_name == 'pull_request' + working-directory: common + run: | + total=$(go tool cover -func=coverage.out | tail -1 | awk '{print $3}' | tr -d '%') + echo "Total project coverage: ${total}%" + awk -v t="$total" 'BEGIN { exit (t+0 >= 95) ? 0 : 1 }' \ + || { echo "::error::Project coverage ${total}% is below the 95% floor"; exit 1; }