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
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.
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.
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).
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds
.gitea/workflows/ci.ymlwith three jobs on runner labelbongbetic-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. SeeCI.md.Commits
ci: add quality-gate workflow:ci.yml,CI.md,.jscpd.json(threshold 8%, baseline 7.15%),ruff==0.16.10in[dev]extras,[tool.ruff.lint] select = ["E4","E7","E9","F"]somake lintmatches CI. No third-partyuses:actions; checkout is shell git. Pins: semgrep 1.178.0, ruff 0.16.10, jscpd 4.3.0, pr-agent 0.47.0.fix: ruff findings: 216 findings to 0.ruff --fixfor unused imports/f-string/redefinition; hand fixes for unused variables (dropped unusedas pilot/as mock_*, kept side-effecting calls), loop variable shadowingtimezoneintui.pyrenamed, two E402s moved, and 4 narrow# noqa: E402on imports that must follow thesys.pathbootstrap incollect.py/monitor.py. Three# noqa: F401intui.pykeep re-exports thattests/test_acceptance_sweep.pyasserts. No behaviour change.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).chore: annotate release.yml semgrep warnings(own commit, review separately): comment-only# nosemgrep: github-actions-mutable-action-tagon the twouses:lines.release.ymlbehaviour andruns-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 COLUMNwith names from hardcoded tuples; DDL cannot bind identifiers. Suppressed.local_day.py:397,1021: query text from constant fragments (local_tzoptional column,AND tz_name = ?); values are bound with?. Suppressed.No SQL is built from external or user input.
Verification (branch tip)
--error: exit 0, 0 findings (380 rules, 95 files).ruff check src/ tests/: exit 0..jscpd.json: 7.06% (threshold 8), exit 0.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).test_tui.py::TestDenseScreen::test_branding_and_one_time_auth_banner. That test passes on its own and when all oftest_tui.pyruns 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.bongbetic-ciand unknowngiteacontext.Notes / deviations
lintinstallspython3-venvvia apt:node:24-bookwormlacksensurepip(verified). ruff itself is pinned; apt packages are not.ai-review: nooptions: --entrypoint "". The image entrypoint ispython pr_agent/cli.py, but the runner replaces the container entrypoint for job containers; the step runspr-agentfrom/app. Not tested on the real runner.config__fallback_modelsis set to the chosen model andconfig__custom_model_max_tokensis 200000 (assumption; adjust per model).OPENROUTER_API_KEY,PR_AGENT_GITEA_TOKEN; optional varPR_AGENT_MODEL.Runner verification
The shared workflow was run on the real
bongbetic-cirunner on a scratch repo:security,lintandai-reviewall passed (ai-reviewskips 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.