Same split as pulsenode's workflow: previously this only ran at all when pushing a version tag, so ruff/pytest never ran on regular commits or PRs - a broken push could sit unnoticed until someone tried to cut a release. Now checks run on every push and PR; publish to the PyPI registry stays gated to a tag push and requires checks to pass first. Renamed release.yaml -> ci.yml to match.