# 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: -- ` 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.