ci: add quality-gate workflow

This commit is contained in:
soubarna
2026-10-05 17:21:50 +05:30
parent 3c07f37c78
commit 2c93874799
4 changed files with 162 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
name: CI
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '0 3 * * 1'
workflow_dispatch:
permissions:
contents: read
jobs:
security:
runs-on: bongbetic-ci
timeout-minutes: 15
container:
image: docker.io/semgrep/semgrep:1.178.0
steps:
- name: Checkout
env:
GIT_TOKEN: ${{ gitea.token }}
run: |
set -euo pipefail
git init -q .
git remote add origin "${{ gitea.server_url }}/${{ gitea.repository }}.git"
git -c http.extraheader="Authorization: token ${GIT_TOKEN}" fetch -q --depth=1 origin "${{ gitea.sha }}"
git checkout -q FETCH_HEAD
- name: Semgrep
run: semgrep scan --config p/default --config p/owasp-top-ten --metrics off --error
lint:
if: gitea.event_name != 'schedule'
runs-on: bongbetic-ci
timeout-minutes: 20
steps:
- name: Checkout
env:
GIT_TOKEN: ${{ gitea.token }}
run: |
set -euo pipefail
git init -q .
git remote add origin "${{ gitea.server_url }}/${{ gitea.repository }}.git"
git -c http.extraheader="Authorization: token ${GIT_TOKEN}" fetch -q --depth=1 origin "${{ gitea.sha }}"
git checkout -q FETCH_HEAD
# node:24-bookworm ships python3 without ensurepip, so python3-venv comes from apt.
- name: Install ruff
run: |
set -euo pipefail
apt-get update -qq
apt-get install -y -qq --no-install-recommends python3-venv
python3 -m venv /tmp/lint-venv
/tmp/lint-venv/bin/pip install -q ruff==0.16.10
- name: Ruff
run: /tmp/lint-venv/bin/ruff check src/ tests/
- name: Duplicate code (jscpd)
run: npx --yes jscpd@4.3.0 --config .jscpd.json .
ai-review:
if: gitea.event_name == 'pull_request'
runs-on: bongbetic-ci
timeout-minutes: 10
continue-on-error: true
container:
image: docker.io/pragent/pr-agent:0.47.0
env:
config__git_provider: gitea
gitea__url: https://git.bongbetic.com
gitea__personal_access_token: ${{ secrets.PR_AGENT_GITEA_TOKEN }}
openrouter__key: ${{ secrets.OPENROUTER_API_KEY }}
config__model: ${{ vars.PR_AGENT_MODEL || 'openrouter/anthropic/claude-sonnet-5' }}
config__fallback_models: "[\"${{ vars.PR_AGENT_MODEL || 'openrouter/anthropic/claude-sonnet-5' }}\"]"
config__custom_model_max_tokens: '200000'
steps:
# Advisory only: posts a review comment, never pushes code. Skips when secrets are absent
# (for example PRs from forks, where Gitea withholds secrets).
- name: PR-Agent review
env:
PR_URL: ${{ gitea.event.pull_request.html_url }}
run: |
set -euo pipefail
if [ -z "${gitea__personal_access_token}" ] || [ -z "${openrouter__key}" ]; then
echo "::notice::PR_AGENT_GITEA_TOKEN or OPENROUTER_API_KEY not set; skipping AI review"
exit 0
fi
cd /app
pr-agent --pr_url="${PR_URL}" review
+18
View File
@@ -0,0 +1,18 @@
{
"threshold": 8,
"minLines": 5,
"minTokens": 50,
"reporters": ["console"],
"gitignore": true,
"ignore": [
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
"**/docs/**",
"**/packaging/**",
"**/*.lock",
"**/package-lock.json",
"**/requirements.txt",
"**/*.md"
]
}
+48
View File
@@ -0,0 +1,48 @@
# CI quality gates
Workflow: `.gitea/workflows/ci.yml` (runner label `bongbetic-ci`, no third-party `uses:` actions; code is checked out with shell git).
Status contexts: `CI / security (pull_request)`, `CI / lint (pull_request)`, `CI / ai-review (pull_request)`.
| Job | Runs on | Blocks merge? | What it does |
|-----|---------|---------------|--------------|
| `security` | PR, push to main, weekly schedule, manual | Yes | Semgrep 1.178.0, `p/default` + `p/owasp-top-ten`, `--error` |
| `lint` | PR, push to main, manual | Yes | `ruff check src/ tests/` (ruff 0.16.10, rules E4/E7/E9/F) and jscpd 4.3.0 duplicate-code threshold 8% |
| `ai-review` | PR only | No (advisory) | PR-Agent `review`, comment-only, `continue-on-error: true` |
There is no `e2e` (no web UI) and no `deploy` job (not a Coolify app). The weekly schedule (`0 3 * * 1`) runs only `security`.
## Run locally
```sh
# security (same command as CI)
podman run --rm -v "$PWD:/src:Z" docker.io/semgrep/semgrep:1.178.0 \
semgrep scan --config p/default --config p/owasp-top-ten --metrics off --error
# lint (needs `pip install -e '.[dev]'` or `pip install ruff==0.16.10`)
make lint
# duplicate code
npx --yes jscpd@4.3.0 --config .jscpd.json .
```
Notes:
- The semgrep container needs no extra flags. `:Z` is only for SELinux hosts; its working directory is `/src`.
- The `lint` job installs `python3-venv` from apt because `node:24-bookworm` has no `ensurepip`; ruff itself is pinned.
- `.jscpd.json` threshold is 8%, just above the 7.15% baseline, so duplication cannot grow. Lower it as duplication is removed.
- Semgrep prints some non-fatal `PartialParsing` errors; they do not fail the job.
## PR-Agent (advisory)
`ai-review` posts a review comment on the PR through the Gitea API. It never pushes code and never blocks. It exits 0 with a notice when its secrets are empty (for example on fork PRs).
Required repository secrets: `OPENROUTER_API_KEY`, `PR_AGENT_GITEA_TOKEN` (Gitea token of the bot account, scopes to comment on PRs).
Optional repository variable: `PR_AGENT_MODEL` (default `openrouter/anthropic/claude-sonnet-5`). The workflow sets `config__custom_model_max_tokens` to 200000, so adjust it if you pick a model with a different context window.
## Caveats
- Semgrep registry rules (`p/default`, `p/owasp-top-ten`) are fetched at runtime and are not pinned, so a new rule can turn a green main red. The weekly scheduled `security` run catches this early.
- Intentional findings are suppressed with a narrow `# nosemgrep: <rule-id> -- <reason>` on the line. Do not use `.semgrepignore` for source files.
## Rollback
Revert the PR that added `ci.yml` (and its follow-up commits). If branch protection requires `CI / security`, `CI / lint` or `CI / ai-review`, relax it first, otherwise merges stay blocked on checks that no longer run.
+4
View File
@@ -13,8 +13,12 @@ dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-asyncio>=0.20.0",
"ruff==0.16.10",
]
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]