agentleFS
the-vibey-projectthe-vibey-project/vibey/.agents/skills/vibey-quality-gates/SKILL.md

vibey-quality-gates

Skill3 starsChanged 25 days agoMITView on GitHub

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.

  • Installs packages
  • Commits and pushes

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If it fails, the error names the contract and the violating import.

    line 77
  2. Very specific:

    Read a runner's [tool.pytest.ini_options] before running anything by hand.

    line 263
  3. Very specific:

    Keep every clone and worktree on storage a reboot keeps (sub-doctrine 10.h, ADR-0057).

    line 272
  4. Distinctive:

    Commit as soon as a change is coherent, not when it is finished.

    line 285

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 with pre-commit run --all-files --hook-stage pre-push (a plain pre-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/vibey

Gate 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=100

Why 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 the vibey-testing skill.

Gate 5: lint-imports

Onion architecture enforcer. Verifies dependencies point inward only, and that domain/ imports nothing but stdlib.

uv run lint-imports

If 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 each interfaces/ 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 every interfaces/ 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/vibey

Gate 7: pip-audit

Dependency vulnerability scanner. Checks for known CVEs in pinned dependencies.

uv run pip-audit

The npm lockfiles (the root workspace and clients/app) get the same gate from vibey-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. 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. Run python3 <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 tracked docs/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. The order matters, because pre-commit install refuses to run while core.hooksPath is set:

pre-commit install && pre-commit install --hook-type pre-push && pre-commit install --hook-type commit-msg
uv run vibey-gh install

vibey-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 prints warning: 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 and macOS, where it also bundles, verifies, launches and images the app):

JobWhat it checks
uv-lockuv 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.
gatesThe seven gates above, against a postgres:17 service. postgres-compatibility additionally runs the database suite on PostgreSQL 14, 15, 16, 17, and 18.
toolsEach 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-lintvibey-gh's own linters and its managed-automation drift check.
imageBuilds 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.
chartRender-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-smokeHelm 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 -q

vibey-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 need uv: 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, and test/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-cov

CI 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 own tests/, 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 (StepJournal in docs/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

GateCatches
ruff checkUnused imports, undefined names, syntax errors, anti-patterns
ruff formatInconsistent code style
mypy --strictType errors, missing annotations, Any usage
pytest (per-layer 100%)Untested branches, logic errors, regressions
lint-importsOnion violations, forbidden imports
banditSecurity issues, hardcoded secrets, insecure patterns
pip-auditDependency CVEs

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.