Skip to main content

cerberus

Verifies repository invariants — CI workflow structure, justfile and dependency conventions, CODEOWNERS, and release-version bumps — as a per-repo linter against a checkout.

Requirements

  • uv and Python 3.14

The justfile bite shells out to just, which ships with the package (via rust-just) — no separate install. The jscpd and fallow bites run their tools via pnpx at exact versions pinned in tool_pins.py, so every cerberus release measures with the same tools everywhere; pnpm must be on PATH.

Lint a repo

uv run cerberus            # lint the current directory
uv run cerberus PATH       # lint a checkout at PATH

Runs every bite and exits non-zero on any failure or error, so it drops into CI like any linter. Run cerberus list to see every bite, its scope, and what it verifies.

Option Description
--check NAME Limit to named bite(s); repeatable
--config PATH Overlay file applied in place of the repo root cerberus.toml
--fix Auto-fix fixable problems (e.g. trailing whitespace)
--verbose/-v Itemize what each bite measured (clones, dead-code issues)

A repo switches a bite off with off = true in that bite's cerberus.toml table (see Config below); naming an off bite with --check still runs it.

Bites

ID Scope Verifies
justfile content Canonical baseline block (byte-exact, --fixable), recipe names, aliases, check pipeline, local cerberus run, wrapped tool calls, no trailing whitespace
ci_workflow_gate content ci.yml exists, exposes a ci check, runs on PRs (push to main recommended)
ci_check_sequence content ci.yml runs the canonical check sequence per stack
ci_cerberus_step content A CI workflow runs cerberus to self-verify org invariants
workflow_toolchain_only content Workflows set up only the workspace toolchain (uv, pnpm), not extra tools
pyrefly content All code, tests included, type-checks under strict pyrefly with no relaxations
ruff content ruff runs standalone in preview with select = ["ALL"]; relaxations stay sanctioned
line_length content ruff line-length and prettier printWidth both match the configured width (120)
rumdl content .rumdl.toml carries the org-canonical rule config (per-repo exclude allowed)
knip content knip config is standalone, never inline in package.json; knip.prod.json runs the entry-exports pass and exempts exactly the repo's published npm targets
vitest content TypeScript tests use Vitest rather than Bun's test runner
tsc content TypeScript typecheck runs via project references (tsc -b), not a per-package fan-out
catalog_pinned_deps content Every workspace package.json dependency pins via catalog: or workspace:
vitest_coverage content Root Vitest coverage thresholds meet the configured floor
package_exports content Libraries use a root barrel or named exports with matching source and publication keys
package_side_effects content Library side-effect declarations include detected registration statements
contract_keepers content Declared application scopes have one keeper with contract surfaces
dependency_direction content Workspace manifest dependencies follow declared direction
project_references content Workspace TypeScript projects reference their compiled dependencies once
worker_runtime content First-party Worker runtime imports avoid filesystem builtins
story_tests_lockstep_py content tests/**/stories/**/*.md criteria have a matching, title-matched pytest test
story_tests_lockstep_ts content Each TypeScript story directory pairs numeric or prefixed criterion IDs with tests
cli_py_test_seam content CLI apps' story tests import only their root module or cli entry module
lib_py_test_seam content Libraries' story tests import only their root module
release_surface_version_bump git-history A published target's version is bumped by exactly one step whenever its release surface changes
codeowners_coverage content CODEOWNERS present and covers /.github/
pytest content pyproject.toml [tool.coverage.report] fail_under meets the floor (90%)
jscpd content Copy-paste duplication per language stays under the configured jscpd threshold
fallow content fallow finds no unused code, circular imports, or functions above its complexity thresholds
zyplux_deps_latest content Every @zyplux/* npm package, zyplux-* PyPI distribution, and ghcr.io/zyplux image is used at its latest release
tool_pins_latest content The npm tool versions pinned in cerberus source are the latest npm releases (skips repos not carrying the pin source)

The justfile baseline

Every repo's justfile must start with the line # BASELINE, carry the canonical block from baseline.just byte-for-byte, and close it with a # CUSTOM line. Everything after # CUSTOM is the repo's own (extra aliases, recipes, set/mod statements, variables). With both markers present, --fix restores a drifted baseline region and leaves the custom tail untouched; the zyplux repo's own justfile mirrors the packaged canonical, and cerberus keeps the two identical.

Config

Every default lives in cerberus.toml: shared source ownership under [source], and check settings under their bite's table ([justfile], [pytest], [jscpd], …). The bundled file is the single home of the defaults; missing required keys are errors. A repo adjusts them with a root cerberus.toml, overlaid key by key. An explicit --config PATH stands in for that repository file. Lists replace the corresponding default list.

Every bite table also takes a common off key, handled by the runner: off = true removes the bite from the run entirely — no output line — and an overlay's off = false re-enables a bite the bundled defaults ship off. tool_pins_latest ships off for exactly that reason: only the repo carrying the pin source can act on it, and that repo's overlay switches it on.

Shared source ownership

[source]
production_roots = ["apps/*", "packages/*", "infra"]
test_files = [
    "**/tests/**",
    "**/__tests__/**",
    "**/*.test.*",
    "**/*.spec.*",
    "**/test_*.py",
    "**/*_test.py",
    "**/conftest.py",
]

Production roots are repository-relative directory globs; their descendants belong to production, including source assets, build configuration, and deployment infrastructure. Test-file globs take precedence even inside a production root. * matches one path segment and ** spans directories. Files outside both selections are other maintained files, such as development tooling. These conventions apply independently of which bites are enabled.

Knip intersects this ownership with registered JavaScript workspaces; a standalone root package remains production. Pyrefly requires coverage of production and test Python source, including flat infra/deploy.py and deeply nested roots. It reports a production root's src subtree when the file lives there. Both checks consume the same classification; [knip].prod_workspaces and [pyrefly].prod_workspaces must be replaced by [source].production_roots, with conflicting lists reconciled by the repository owner.

Other tools can consume the public API without scanning files or invoking the CLI:

from pathlib import Path
from cerberus import load_source_scope

scope = load_source_scope(Path("/path/to/repository"))
scope.is_production_file("infra/deploy.py")  # True with bundled defaults
scope.is_test_file("apps/widget/tests/widget.test.tsx")  # True
scope.find_production_root("apps/widget/src/widget.tsx")  # "apps/widget"

load_source_scope reads the defaults and repository overlay. The returned SourceScope classifies repository-relative POSIX paths without filesystem access; the caller owns file discovery and any generated-file filtering.

zyplux_deps_latest queries npm, PyPI, and GHCR at lint time; a failed lookup is reported as an error, never a silent pass. It has no --fix — run just upgrade to catch up.

tool_pins_latest guards the jscpd/fallow pins the same way, but runs only in the repo that carries tool_pins.py — the one place a pin can be bumped. Consumer repos never see it (bundled off = true) and pick new pins up with the next cerberus release, which zyplux_deps_latest already forces them onto.

Shared architecture policy

Declare application contract keepers and allowed workspace dependencies in [architecture]. ESLint reads the same declaration; see its configuration examples.

TypeScript seam checks use ESLint's resolved imports. The retired cli_ts_test_seam, lib_ts_test_seam, and fixture_roles_ts IDs report migration guidance when encountered in overlays. Their package policies are covered by the focused export, ownership, and reference checks.

Worker traversal covers first-party static imports and literal dynamic imports. Computed imports, third-party modules, and framework-provided entries are reported as static coverage limits; production bundle validation covers those boundaries. Side-effect detection checks top-level registration statements and cannot prove an arbitrary dependency graph pure.

Metadata

Release files for zyplux-cerberus 0.20.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zyplux-cerberus 0.20.0
File Size Uploaded
zyplux_cerberus-0.20.0.tar.gz 67.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zyplux-cerberus 0.20.0
File Interpreter ABI Platform
zyplux_cerberus-0.20.0-py3-none-any.whl Python 3 none any Details

Total release size: 167.4 kB

Release files / zyplux_cerberus-0.20.0.tar.gz

Download URL zyplux_cerberus-0.20.0.tar.gz
Size 67.4 kB
Tags Source
SHA-256 checksum
How to use checksums
9b288c580382b29b84b284a95182b8946e834ef2f048c2c64525185fdd18f6bd
BLAKE2b-256 checksum
How to use checksums
de3d727fcad8e0fc09f54beab970e4484599a68cfca48a3191f9965c12352e2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zyplux_cerberus-0.20.0-py3-none-any.whl

Download URL zyplux_cerberus-0.20.0-py3-none-any.whl
Size 100.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9a0c8d3565e6637756754a086c35a45f10942f3a4653a28173fd8ddb5bcd65db
BLAKE2b-256 checksum
How to use checksums
f52e9004254f96abc219f23fd02eb4a9edaf0c004d7731c58e66b198d65f4098
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.20.0 This release

2 release files

0.18.0

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page