Add CI (Forgejo Actions) + green quality baseline: run the declared dev tools (black/ruff/mypy) and gate on them #31
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
agent-proxyhas no CI and its declared dev tooling is never actually run, so every landing is ungated and formatting/typing drift accumulates silently. This is pure tower-independent cleanup - none of it needs the tower reachable, and all of it is locally verifiable withward testand the dev tools already inpyproject.toml.Scoped from the read-only director surface. Baseline observed on
mainat filing time:ward testgreen.uv run ruff check .- all checks pass.uv run black --check .wants to reformat 17 of 23 files. There is no[tool.black],[tool.ruff], or[tool.mypy]config inpyproject.toml, so the declared dev tools have never been run against the tree.uv run mypy appreports 17 errors in 5 files - a mix of missing third-party stubs (boto3inconfig.py,sentry_sdkinobs.py) and real type gaps (queue.pyNone-handling on the optionalQueue,resilience.pypassingRequestTraceContext | Nonewhere non-optional is expected in 7 spots,obs.pydict variance,main.py:410hypercornservearg type)..forgejo/workflows/directory exists.Why these couple
CI is only worth adding if the tree is green under the gates it runs. If a workflow lands while black and mypy are red, CI is red on arrival. So the format-normalization and the mypy cleanup are prerequisites for the CI gate, and all three belong in one coherent landing rather than parallel runs.
Scope (do all of it in one run, commit in clean stages)
[tool.black]withline-length = 100(or match whatever the codebase already trends to - pick one and set it), add a matching[tool.ruff]line-length, then runuv run black .and commit the reformat as its own commit so the CI commit stays reviewable. Keep black as the formatter - it is already a declared dev dependency, no new deps.[tool.mypy]withignore_missing_importsoverrides (or per-module[[tool.mypy.overrides]]) forboto3andsentry_sdk, then fix the real type errors inqueue.py,resilience.py,obs.py, andmain.pysouv run mypy appis clean. If any single fix looks behavior-risky or uncertain, do NOT force it - make mypy advisory (non-blocking /continue-on-errorin CI) for that case and file a follow-up issue naming the residual, rather than changing runtime behavior to satisfy the checker..forgejo/workflows/ci.yml). On push and PR, run the full local gate offpyproject.toml:uv sync --extra dev, thenward test(pytest),uv run ruff check .,uv run black --check ., anduv run mypy app. Also run the daemonless smoke paths that need no Docker daemon:./boot_probe.shand./test-fixes.sh. Do NOT put the Docker-daemon acceptance test (test-container.sh) or any tower-dependent script (scripts/truncation_proof.py,scripts/reliability_loop.py) in CI - those need a daemon or the tower and would break a daemonless runner.test-fixes.shintoward.yamlas a verb (e.g.smoke: ./test-fixes.sh) so the orphan daemonless smoke test is discoverable and CI calls it through ward, consistent with the other verbs.docs/FEATURES.mdper the trifecta rule, and mention the CI gate inREADME.md's developer-workflow section.Constraints
ward test,uv run ruff check .,uv run black --check .,uv run mypy app,./boot_probe.sh, and./test-fixes.shall green.Done condition
.forgejo/workflows/ci.ymlexists and encodes the gate above, the tree is green under black/ruff/mypy/pytest/boot_probe/test-fixes locally,ward.yamlexposes the smoke verb, docs updated, landed onmain.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-agent-proxy-31on hostkais-macbook-pro-2.localis carrying this issue (reserved 2026-07-02T17:05:06Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.— Claude (she/her), via
ward agentWARD-OUTCOME: done -
.forgejo/workflows/ci.ymlgates pytest/ruff/black/mypy + daemonless smoke; tree normalized and green under all four, landed onmain.Honestly, this one behaved. The baseline in the issue was exact - 17 unformatted files, 17 mypy errors, same files - so there were no surprises hiding in the diff. The reformat is pure mechanical churn, staged on its own commit so the CI landing stays readable.
The mypy pass was more satisfying than expected: the 7
resilience.pyerrors and the 3obs.pyvariance errors all collapsed into one real fix -request_log_fieldsgenuinely could not take theNoneits callers were feeding it, so teaching it to acceptRequestTraceContext | Nonewas a correctness improvement the checker surfaced, not a checker-appeasement hack. Thequeue.pyfuture-guarding and the single scopedtype: ignoreon hypercorn's over-narrowservestub were the only truly defensive touches. No behavior changed beyond that None-safety hardening.The one judgement call: the issue asked CI to invoke
ward test/ward smoke, butwardis a Go binary from a private brew tap and the stockdockerrunner has none of the flight-deck tooling on PATH (cli-guard and infrastructure/lint.yml both install their own tools and calluv rundirectly). Requiringwardwould make CI red on arrival, which the issue explicitly warns against. So CI calls the rawuv run/ script commands, andward.yamlkeeps thesmokeverb as the local source of truth. Worth noting:ward <verb>doesn't resolve config for any verb in this container (test/boot-probe included), so that quirk is orthogonal to my change.Confidence is high - every gate is verified locally green (pytest 58, ruff, black --check, mypy clean, boot_probe, test-fixes). The only unverifiable piece is whether an Actions runner labelled
dockeris actually attached to this repo; if not, the workflow is still the correct artifact and starts gating the moment one is. No follow-ups worth filing.— Claude (she/her), via
ward agent