The 7-gate CI sweep (ruff check, ruff format, mypy, per-layer coverage × 4, lint-imports, bandit, pip-audit), what each gate catches, the other CI jobs, and the workspace tenants' own checks.
Install
npx skillscat add the-vibey-project/vibey/vibey-quality-gates Install via the SkillsCat registry.
vibey quality gates
CI runs a 7-gate sweep over the root vibey package, as the gates job in.github/workflows/ci.yml. All seven must pass — together with the other CI
jobs below — before a PR can merge. Run them locally withpre-commit run --all-files --hook-stage pre-push (a plainpre-commit run --all-files runs only the ruff hooks) or individually:
Gate 1: ruff check
Linter. Catches unused imports, undefined names, syntax errors, and common
anti-patterns.
uv run ruff check .Root ruff covers src/**/*.py and tests/**/*.py, so it also lints the
workspace tenants under src/vibey_runners/ and src/vibey_tools/.
Gate 2: ruff format --check
Formatter. Ensures consistent code style across the repo. Fails if any file
would be reformatted.
uv run ruff format --check .To fix: uv run ruff format .
Gate 3: mypy --strict
Type checker. Enforces --strict mode across src/vibey/. Catches type
errors, missing type annotations, and Any usage.
uv run mypy --strict src/vibeyGate 4: pytest per-layer coverage (100%)
One test run produces a combined .coverage file; four per-layer reports
enforce 100% branch coverage. Each must pass or the build fails.
uv run pytest -q -p no:cacheprovider --cov=vibey --cov-branch --cov-report=
uv run coverage report --include='src/vibey/domain/*' --fail-under=100
uv run coverage report --include='src/vibey/application/*' --fail-under=100
uv run coverage report --include='src/vibey/infrastructure/*' --fail-under=100
uv run coverage report --include='src/vibey/cli/*' --fail-under=100Why per-layer, not aggregate? Because domain/ is the safety-critical
layer and must never drop below 100%. An aggregate gate would allow a 95%
domain to hide behind a 100% infrastructure. See ADR-0023.
The test run needs a reachable Postgres even for a subset: tests/conftest.py
clones a migrated template database per xdist worker at session start. See thevibey-testing skill.
Gate 5: lint-imports
Onion architecture enforcer. Verifies dependencies point inward only, and
that domain/ imports nothing but stdlib.
uv run lint-importsIf it fails, the error names the contract and the violating import. See.importlinter for the contracts: onion-layers, domain-independence,application-independence, and the declare-only contracts that keep eachinterfaces/ package from importing its consumers (interfaces-declare-only
for application/interfaces, infrastructure-interfaces-declare-only,vibey-gh-interfaces-declare-only). The declare-only rule applies to everyinterfaces/ package. See ADR-0016.
See the vibey-architecture skill before fixing a violation.
Gate 6: bandit
Security linter. Scans for common security issues: hardcoded passwords, SQL
injection, insecure temp files, weak crypto, shell=True, etc.
uv run bandit -q -r src/vibeyGate 7: pip-audit
Dependency vulnerability scanner. Checks for known CVEs in pinned
dependencies.
uv run pip-auditThe npm lockfiles (the root workspace and clients/app) get the same gate fromvibey-gh advisory-check in the @vibey/core, app and vscode-extension jobs:npm audit at [advisories] audit_level (high), less the declared exceptions in.github/advisory-exceptions.toml. An exception is never a silent skip: it names one
advisory, package and workspace, records why the vulnerable code is not reached, and
expires within max_exception_days (30). Every run prints each exception it honours and
until when, and fails when one has expired, matches nothing (remove it), or its package has
a patched release (take the fix). The file is a protected path, so accepting a risk takes the
code owner's approval and a human merge. An outage of the advisory service itself is handled
separately: on_unavailable = "fail" is the default, and this repository sets "warn", so a
nonresponsive endpoint prints ::warning:: naming the workspace and the reason and continues
unaudited; a real advisory, a malformed report or a missing lockfile still fails.SECURITY.md, "Dependency advisories", has the rest.
Hook diet
The seven gates are split across git hook stages so commits stay fast
(.pre-commit-config.yaml):
Pre-commit stage (runs on every git commit):
- ruff (
--fix) + ruff format, on changed files
Pre-push stage (runs on every git push):
- The plain parallel test suite (
uv run pytest -q -p no:cacheprovider) — kept
so a test failure reports as itself rather than as a coverage-gate failure - mypy --strict
- lint-imports
- Per-layer 100% coverage gates (pytest --cov + four reports)
- bandit
- pip-audit
Commit-msg stage: Conventional Commits enforcement.
Pushing: push through the push gate, never with a bare git push. Runpython3 <storm>/tools/push_gate.py run -- git push origin HEAD:<branch>, or from a
checkout with no storm, set VIBEY_PUSH_LOCK=<dir> and run the trackeddocs/plans/qwenstorm-3.0.0/tools/push_gate.py. It lets one pre-push run happen at a time
across every lane on the machine. Exit 124 (reaped: hang) means the reaper stopped a hung
gate run; it is not a test failure. CONTRIBUTING.md, "Pushing in this repository", has the
rest.
Install all three hook types once, then the provenance hooks, with one idempotent
command (the order matters, because pre-commit install refuses to run whilecore.hooksPath is set, and the command does it in that order):
uv run --extra dev vibey-gh setupvibey-gh install points core.hooksPath at .githooks. The tracked shims there
(pre-commit, commit-msg.local, pre-push.local) chain back to the framework
through git rev-parse --git-common-dir, so one install covers the main checkout
and every linked worktree (git worktree add). If a shim printswarning: the pre-commit framework's <stage> hook is not installed, the commit or
push went ahead but none of that stage's gates ran. Run them by hand before you push.
The other CI jobs
ci.yml runs fourteen jobs on every push and PR to develop/main. The seven
below are the Python and deployment gates; the others are noloss (the no-loss
property suite at 10,000 examples), postgres-compatibility (the database suite on
PostgreSQL 14 to 18), krypton-app, vibey-core (@vibey/core), app (the krypton
app, mobile and web), vscode-extension (Ubuntu and macOS) and desktop (the krypton
desktop client on Ubuntu, Arch, Fedora and macOS, where it also bundles, verifies, launches and
images the app):
| Job | What it checks |
|---|---|
uv-lock |
uv lock --check. gates, tools and tools-lint depend on it. The lock carries vibey's own version, so a version bump without uv lock fails here first. |
gates |
The seven gates above, against a postgres:17 service. postgres-compatibility additionally runs the database suite on PostgreSQL 14, 15, 16, 17, and 18. |
tools |
Each absorbed tenant's own suite on its own Python floors, plus, on its floor row, its own static gates from the row's static key: its mypy, lint-imports and bandit. codexloop and vibey-skills also run their own strict docs builds (the docs key) (ADR-0022). |
tools-lint |
vibey-gh's own linters and its managed-automation drift check. |
image |
Builds deploy/docker/Dockerfile for amd64 and arm64 and asserts each Image contract - … step: the entrypoint runs, it runs as non-root uid 10001, it has no compiler/uv/pip, migrations ship in the image, and every console script is on PATH. |
chart |
Render-only: deploy/helm/golden/render.sh runs helm lint --strict and helm template for each profile (defaults, ollama.enabled, the GPU + gptossloop wiring with qwenloop switched on beside it, and the KEDA query unbound and bound to a project) and diffs each render against its committed golden under deploy/helm/golden/, with helm pinned. After an intended chart change, regenerate with deploy/helm/golden/render.sh --update. |
cluster-smoke |
Helm install of deploy/helm/vibey on minikube and four cluster contracts: a projectless worker parks instead of crash-looping, the worker picks up a project created in-cluster, the KEDA ScaledObject reconciles against real Postgres, and a worker drains promptly on SIGTERM (ADR-0025, ADR-0026). |
Other workflows also gate a merge: provenance.yml (runs on every push and PR),
and pr-evaluate.yml, which re-evaluates a PR each time CI or Provenance
completes and dispatches pr-review.yml for the review ([pr_automation] scan_workflows in .vibey-gh.toml) and hands ready
PRs to merge-train.yml (see the vibey-releasing skill).
Workspace tenants: their own checks
The tenants under src/vibey_runners/ and src/vibey_tools/ keep the gates they
arrived with (ADR-0021, ADR-0022). The root mypy, bandit and coverage gates
cover src/vibey only. Run a tenant's checks from its own directory, with plain
pip and a Python at its floor — every tenant now shares the 3.12 floor, so the
matrix exercises the common 3.12–3.14 range. Plain pip knows nothing
about [tool.uv.sources] workspace = true, so a tenant that needs a sibling
installs that sibling from the tree first -- no family package may be
requested from an index, because none of them is published any more (ADR-0037).
These are the tools and tools-lint commands, verbatim:
# vibey-gh (CI: Python 3.12, 3.13, 3.14; 100% branch floor in its own addopts)
cd src/vibey_tools/gh
pip install -e ".[dev]"
python -m pytest -q
python -m black --check vibey_gh test
isort --check-only vibey_gh test
python -m mypy vibey_gh
# vibey-skills (CI: Python 3.12, 3.13, 3.14)
cd src/vibey_tools/skills
pip install -e .
python3 tools/validate_manifests.py && python3 tools/check_links.py \
&& PYTHONPATH=src python3 -m unittest discover -s tests
# vibey-bootstrap (CI: Python 3.12, 3.13, 3.14). It imports vibey_gh, so install that first.
cd src/vibey_tools/bootstrap
pip install -e ../gh && pip install -e ".[test,all]"
pytest test/ -m "not integration" --cov=vibey_bootstrap --cov-report=term
# claudeloop (CI: Python 3.12, 3.13, 3.14 -- the common library floor)
cd src/vibey_runners/claude
pip install -e ../common && pip install -e ".[dev]"
pytest tests/domain --cov=claudeloop.domain --cov-branch --cov-report=term-missing --cov-fail-under=100
pytest tests/application --cov=claudeloop.application --cov-branch --cov-report=term-missing --cov-fail-under=100
pytest tests/infrastructure -n auto --maxprocesses=8 --cov=claudeloop.infrastructure --cov-branch --cov-report=term-missing --cov-fail-under=100
pytest tests/cli -n auto --maxprocesses=8 --cov=claudeloop.cli --cov-branch --cov-report=term-missing --cov-fail-under=100
# codexloop (CI: Python 3.12, 3.13).
cd src/vibey_runners/codex
pip install -e ../common && pip install -e ".[dev]"
pytest -q --cov=codexloop --cov-branch --cov-report=
coverage report --include='src/codexloop/domain/*' --fail-under=100
coverage report --include='src/codexloop/application/*' --fail-under=100
coverage report --include='src/codexloop/infrastructure/*' --fail-under=100
coverage report --include='src/codexloop/cli/*' --fail-under=100
# qwenloop (CI: Python 3.12, 3.13, 3.14; its floor is already in its addopts)
cd src/vibey_runners/qwen
pip install -e ".[dev]"
python -m pytest -qvibey-gh's suite drives real git history and shells out to gh, so it needs
both on PATH and a full clone -- a shallow one fails. It does not needuv: the single test that drives a real uv lock is marked network, below.
Tests that leave the machine (a real package index, a real remote) carry@pytest.mark.network and are skipped unless VIBEY_GH_NETWORK_TESTS=1.
Marker and variable are two halves of one mechanism -- the marker declares, andtest/conftest.py does the skipping -- so a plain offline python -m pytest
reaches the package's 100% branch floor with no flag to remember, and a run that
reports 4 skipped is correct rather than degraded. Every branch a network
test touches must also be reachable offline, or that floor fails. Where an index
is reachable, ask for them by name:
cd src/vibey_tools/gh
VIBEY_GH_NETWORK_TESTS=1 python -m pytest -q -m network --no-covCI runs that on one matrix row with continue-on-error: it reports on somebody
else's service, so it must never gate a merge.
The runners (src/vibey_runners/{claude,codex,qwen}) carry their owntests/, ruff, mypy and import-linter configuration in their pyproject.toml.
Since 2026-09-15 the root tools matrix runs all three, one row per runner and Python
version (read the rows in ci.yml), so a runner regression turns the tools job red. It reports rather than blocks: tools
is not named in required_checks in .vibey-gh.toml, and no tools row ever has been.
Read a runner's [tool.pytest.ini_options] before running anything by hand. Only qwenloop's
addopts carry a coverage floor. The other two deliberately leave --cov= and--cov-fail-under out, because pytest-cov unions every --cov= it sees and one baked
in there would widen the explicitly-scoped per-layer runs back out to the whole package
-- so for them a bare pytest runs the tests but enforces no floor. Use the commands
above, which are their own.
Where work lives, and how often it is saved
Keep every clone and worktree on storage a reboot keeps (sub-doctrine 10.h, ADR-0057). Never
use /tmp, /private/tmp, /var/tmp, /var/folders, /dev/shm, /run/user or $TMPDIR:
the OS empties them. On 2026-09-24 a reboot emptied /private/tmp mid-storm and took every
uncommitted worktree, measurement and draft with it. Worktrees go in the storm home:VIBEY_STORM_HOME, else ~/git/vibey-storm on macOS, $XDG_DATA_HOME/vibey/storm (else~/.local/share/vibey/storm) on Linux.
python3 docs/plans/qwenstorm-3.0.0/tools/storm_durability.py status # durable or not
git worktree add "$(python3 docs/plans/qwenstorm-3.0.0/tools/storm_durability.py worktree fix-x)" \
-b fix/x origin/develop- Commit as soon as a change is coherent, not when it is finished.
- Push work in progress to a draft PR (
gh pr create --draft) at least every 30–45 minutes;
the merge train never merges a draft, and the pre-push gates still run. - A long measurement writes each step as it finishes and resumes (
StepJournalindocs/plans/qwenstorm-3.0.0/tools/storm_checkpoint.py). - The storm tools refuse volatile storage with exit 78 and name the key to change.
CONTRIBUTING.md, "Where your work lives", has the rest.
What each gate catches
| Gate | Catches |
|---|---|
| ruff check | Unused imports, undefined names, syntax errors, anti-patterns |
| ruff format | Inconsistent code style |
| mypy --strict | Type errors, missing annotations, Any usage |
| pytest (per-layer 100%) | Untested branches, logic errors, regressions |
| lint-imports | Onion violations, forbidden imports |
| bandit | Security issues, hardcoded secrets, insecure patterns |
| pip-audit | Dependency CVEs |