ci: add quality-gate workflow (semgrep, ruff, jscpd, advisory PR-Agent) #112

Merged
xavierk merged 4 commits from ci/quality-gates into main 2026-10-05 14:14:59 +00:00
Owner

Summary

Adds .gitea/workflows/ci.yml with three jobs on runner label bongbetic-ci: security (Semgrep, blocking), lint (ruff + jscpd, blocking), ai-review (PR-Agent, advisory). Existing ruff and Semgrep findings are cleaned up so the gates are green from the first run. See CI.md.

Commits

  1. ci: add quality-gate workflow: ci.yml, CI.md, .jscpd.json (threshold 8%, baseline 7.15%), ruff==0.16.10 in [dev] extras, [tool.ruff.lint] select = ["E4","E7","E9","F"] so make lint matches CI. No third-party uses: actions; checkout is shell git. Pins: semgrep 1.178.0, ruff 0.16.10, jscpd 4.3.0, pr-agent 0.47.0.
  2. fix: ruff findings: 216 findings to 0. ruff --fix for unused imports/f-string/redefinition; hand fixes for unused variables (dropped unused as pilot/as mock_*, kept side-effecting calls), loop variable shadowing timezone in tui.py renamed, two E402s moved, and 4 narrow # noqa: E402 on imports that must follow the sys.path bootstrap in collect.py/monitor.py. Three # noqa: F401 in tui.py keep re-exports that tests/test_acceptance_sweep.py asserts. No behaviour change.
  3. fix: triage semgrep SQL findings: 22 findings (12 ERROR + 10 WARNING) reviewed; all are constant-only SQL, so each got a narrow # nosemgrep: <rule> -- <reason> (none needed parameterising, see below).
  4. chore: annotate release.yml semgrep warnings (own commit, review separately): comment-only # nosemgrep: github-actions-mutable-action-tag on the two uses: lines. release.yml behaviour and runs-on: [self-hosted] untouched.

Semgrep triage

  • pruning.py (5 lines): SAVEPOINT {savepoint} where the name is a function-local constant; SQLite cannot bind identifiers. Suppressed.
  • store.py:63,438,314: PRAGMA user_version= with an int constant / int key of the static migrations map; PRAGMA cannot bind params. Suppressed.
  • store.py:339,396: ALTER TABLE ... ADD COLUMN with names from hardcoded tuples; DDL cannot bind identifiers. Suppressed.
  • local_day.py:397,1021: query text from constant fragments (local_tz optional column, AND tz_name = ?); values are bound with ?. Suppressed.
    No SQL is built from external or user input.

Verification (branch tip)

  • Semgrep via podman, same command with --error: exit 0, 0 findings (380 rules, 95 files).
  • ruff check src/ tests/: exit 0.
  • jscpd 4.3.0 with .jscpd.json: 7.06% (threshold 8), exit 0.
  • pytest: main baseline is 865 passed, 43 skipped, 1 failed. The failure is test_status_composition.py::TestTUIIntegration::test_tui_renders_monitoring_glyph, which fails on main too (the fixture sample is reported as stale, so the glyph differs; unrelated to this PR).
  • pytest on the branch tip, 4 full runs: one matched the baseline; one failed only the baseline test; two also failed test_tui.py::TestDenseScreen::test_branding_and_one_time_auth_banner. That test passes on its own and when all of test_tui.py runs on the tip, and it also passed twice alone on main. It looks flaky, but it was never seen failing on main, so it needs a closer look before merge. Tests were not run on the real runner.
  • YAML parses; actionlint reports only false positives: unknown runner label bongbetic-ci and unknown gitea context.

Notes / deviations

  • lint installs python3-venv via apt: node:24-bookworm lacks ensurepip (verified). ruff itself is pinned; apt packages are not.
  • ai-review: no options: --entrypoint "". The image entrypoint is python pr_agent/cli.py, but the runner replaces the container entrypoint for job containers; the step runs pr-agent from /app. Not tested on the real runner. config__fallback_models is set to the chosen model and config__custom_model_max_tokens is 200000 (assumption; adjust per model).
  • Not run on the actual Gitea runner; first PR run is the real test. Required secrets: OPENROUTER_API_KEY, PR_AGENT_GITEA_TOKEN; optional var PR_AGENT_MODEL.
  • Semgrep registry rules are unpinned; weekly schedule catches regressions. Rollback: revert this PR (relax branch protection first if it requires these checks).

Runner verification

The shared workflow was run on the real bongbetic-ci runner on a scratch repo: security, lint and ai-review all passed (ai-review skips with a notice until the two secrets exist). A first scratch run exposed an --entrypoint "" override that made the pr-agent container exit; it is not in this workflow. This repo's own CI run on this PR is the first real run of its repo-specific steps.

## Summary Adds `.gitea/workflows/ci.yml` with three jobs on runner label `bongbetic-ci`: `security` (Semgrep, blocking), `lint` (ruff + jscpd, blocking), `ai-review` (PR-Agent, advisory). Existing ruff and Semgrep findings are cleaned up so the gates are green from the first run. See `CI.md`. ## Commits 1. `ci: add quality-gate workflow`: `ci.yml`, `CI.md`, `.jscpd.json` (threshold 8%, baseline 7.15%), `ruff==0.16.10` in `[dev]` extras, `[tool.ruff.lint] select = ["E4","E7","E9","F"]` so `make lint` matches CI. No third-party `uses:` actions; checkout is shell git. Pins: semgrep 1.178.0, ruff 0.16.10, jscpd 4.3.0, pr-agent 0.47.0. 2. `fix: ruff findings`: 216 findings to 0. `ruff --fix` for unused imports/f-string/redefinition; hand fixes for unused variables (dropped unused `as pilot`/`as mock_*`, kept side-effecting calls), loop variable shadowing `timezone` in `tui.py` renamed, two E402s moved, and 4 narrow `# noqa: E402` on imports that must follow the `sys.path` bootstrap in `collect.py`/`monitor.py`. Three `# noqa: F401` in `tui.py` keep re-exports that `tests/test_acceptance_sweep.py` asserts. No behaviour change. 3. `fix: triage semgrep SQL findings`: 22 findings (12 ERROR + 10 WARNING) reviewed; all are constant-only SQL, so each got a narrow `# nosemgrep: <rule> -- <reason>` (none needed parameterising, see below). 4. `chore: annotate release.yml semgrep warnings` (own commit, review separately): comment-only `# nosemgrep: github-actions-mutable-action-tag` on the two `uses:` lines. `release.yml` behaviour and `runs-on: [self-hosted]` untouched. ## Semgrep triage - `pruning.py` (5 lines): `SAVEPOINT {savepoint}` where the name is a function-local constant; SQLite cannot bind identifiers. Suppressed. - `store.py:63,438,314`: `PRAGMA user_version=` with an int constant / int key of the static migrations map; PRAGMA cannot bind params. Suppressed. - `store.py:339,396`: `ALTER TABLE ... ADD COLUMN` with names from hardcoded tuples; DDL cannot bind identifiers. Suppressed. - `local_day.py:397,1021`: query text from constant fragments (`local_tz` optional column, ` AND tz_name = ?`); values are bound with `?`. Suppressed. No SQL is built from external or user input. ## Verification (branch tip) - Semgrep via podman, same command with `--error`: exit 0, 0 findings (380 rules, 95 files). - `ruff check src/ tests/`: exit 0. - jscpd 4.3.0 with `.jscpd.json`: 7.06% (threshold 8), exit 0. - pytest: main baseline is 865 passed, 43 skipped, 1 failed. The failure is `test_status_composition.py::TestTUIIntegration::test_tui_renders_monitoring_glyph`, which fails on main too (the fixture sample is reported as stale, so the glyph differs; unrelated to this PR). - pytest on the branch tip, 4 full runs: one matched the baseline; one failed only the baseline test; two also failed `test_tui.py::TestDenseScreen::test_branding_and_one_time_auth_banner`. That test passes on its own and when all of `test_tui.py` runs on the tip, and it also passed twice alone on main. It looks flaky, but it was never seen failing on main, so it needs a closer look before merge. Tests were not run on the real runner. - YAML parses; actionlint reports only false positives: unknown runner label `bongbetic-ci` and unknown `gitea` context. ## Notes / deviations - `lint` installs `python3-venv` via apt: `node:24-bookworm` lacks `ensurepip` (verified). ruff itself is pinned; apt packages are not. - `ai-review`: no `options: --entrypoint ""`. The image entrypoint is `python pr_agent/cli.py`, but the runner replaces the container entrypoint for job containers; the step runs `pr-agent` from `/app`. Not tested on the real runner. `config__fallback_models` is set to the chosen model and `config__custom_model_max_tokens` is 200000 (assumption; adjust per model). - Not run on the actual Gitea runner; first PR run is the real test. Required secrets: `OPENROUTER_API_KEY`, `PR_AGENT_GITEA_TOKEN`; optional var `PR_AGENT_MODEL`. - Semgrep registry rules are unpinned; weekly schedule catches regressions. Rollback: revert this PR (relax branch protection first if it requires these checks). ## Runner verification The shared workflow was run on the real `bongbetic-ci` runner on a scratch repo: `security`, `lint` and `ai-review` all passed (`ai-review` skips with a notice until the two secrets exist). A first scratch run exposed an `--entrypoint ""` override that made the pr-agent container exit; it is not in this workflow. This repo's own CI run on this PR is the first real run of its repo-specific steps.
xavierk added 4 commits 2026-10-05 12:11:47 +00:00
chore: annotate release.yml semgrep warnings
CI / security (pull_request) Successful in 22s
CI / lint (pull_request) Successful in 33s
CI / ai-review (pull_request) Successful in 2s
e3b6f89ebb
xavierk merged commit cd053cf125 into main 2026-10-05 14:14:59 +00:00
Sign in to join this conversation.