Shared quality gates and reusable GitHub Actions for the Knuckles-Team agent ecosystem. The package centralizes shared hook and release policy. Consumers supply immutable references and repository-specific inputs.
pipelines-hooks is a small Python package with the pipelines-hook command. It publishes repository-agnostic pre-commit gates. The reusable workflows supply tested building blocks for Python, native, container, service, desktop, and Pages delivery.
The spec dashboard template adds a source-backed delivery snapshot to existing Pages publishing.
- Repository-local TOML configuration with strict unknown-key validation.
- Security, privacy, supply-chain, hygiene, code-shape, and clone gates.
- A public-surface gate that catches dead README/AGENTS links and a missing quick start.
- Controlled-language gates that check document prose and stale claims. The ste reference states the standard and the configuration.
- Reusable GitHub workflows pinned by callers to reviewed immutable commits.
- Pages readiness and shared MkDocs theme assets for deeper documentation.
The Pages site contains the navigable reference surface. The Pages readiness guide covers generated manifests, content-source configuration, and delivery checks. Public specifications define upcoming pipeline-owned work and contribution contracts.
The hook catalogue in .pre-commit-hooks.yaml is the authoritative list of published hook IDs. Repository-specific configuration is described in the Pages reference.
The pipelines-hook entry point dispatches to one gate module. Gate inputs are read from [tool.pipelines_hooks] in the consuming repository. The shared implementation never contains a product-specific allowlist. Gates return zero for clean, one for findings, and two when they cannot produce a trustworthy verdict. A gate whose native scanner or sibling checkout is absent fails closed with two when CI is set. Locally it prints SKIPPED (<gate>): <reason>; run scripts/bootstrap.sh [--scanners] and returns zero.
File inputs live under .config/, never at the repository root: .config/repo-layout.toml (root-hygiene allowlist), .config/kiss.toml (KISS thresholds) and .config/dupehound-distinct.toml (reviewed non-clone register). A copy left at the retired root location fails the gate with exit status two.
Reusable workflows are called by another repository's own GitHub Actions workflow. They run with that caller's checkout, permissions, and secrets. A caller pins first-party workflows to a full commit SHA and third-party actions to reviewed immutable revisions.
Install the hook package, then run the public-surface check from a repository:
python -m pip install pipelines-hooks
pipelines-hook public-surfaceFor the shared gates, add the secrets, tests-fast and spec hooks to .config/pre-commit.yaml with rev: main (see the gate standard), and run:
pre-commit run -c .config/pre-commit.yaml --all-filesThe Pages documentation linked above has the complete hook catalogue and workflow-specific setup steps.
Clone the repository, then run scripts/bootstrap.sh for the locked environment, test dependencies, and git hooks. Add --scanners for the native scanners. Run focused tests before the complete suite:
scripts/bootstrap.sh
uv run --frozen python -m pytest -q tests/hooks tests/test_pages_readiness.py
uv run --frozen python -m pytest -q
uvx pre-commit run --config .config/pre-commit.yaml --all-filesOpen a pull request against main from a topic branch; CI runs the same pre-commit configuration and the affected tests. Main and release tags run the full suite and the dependency audit. The advisory scanners report weekly and never block.
Every new hook invariant needs positive and adversarial fixtures. Workflow changes need contract tests for inputs and permissions. Stage only reviewed files and keep generated environment output untracked.
The package is distributed under the license declared by the project metadata. See the repository metadata and published distribution for the applicable terms.
The wheel readiness contract describes metadata-derived runtime profiles, release-only public-index proof, and current release blockers. Consumer migrations must wait for a reviewed immutable guard ref.
The manual packaging-roles hook validates an explicit role/extra contract against
one exact wheel and its existing readiness receipt:
pipelines-hook packaging-roles --contract <path> --wheel <path> --wheel-sha256 <digest> --receipt <path>.
Release callers also use the exact PyPI publication identity checker to reject same-version filename/digest conflicts. It checks every staged file after upload. This supplements their existing readiness and platform-completeness gates.
pipelines-hook ste-staged --base-ref <commit> checks new prose findings in a committed range.
GitHub PR and push runs resolve the base from the event payload. Local commits check the staged index.
See STE comparison rules.
Run pipelines-hook validate --dry-run to inspect .config/validation.toml,
then pipelines-hook validate to run its declared offline repository checks.
The command uses installed tools; it never installs dependencies. It prints a
short group summary and paths to a JSON report and per-check logs. Required
findings return 1; unavailable checks and timeouts return 2. Advisory outcomes
remain visible. Post-merge, post-checkout, and post-rewrite events are inactive.
Version 1 supports root-hygiene, gitignore-convergence, and sprawl, each
at most once. Policy requires version 1, a total budget_seconds (1–300), and
[[checks]] entries with id, group = "repository", gate, required,
timeout_seconds (1–300), nonempty acceptance IDs, and a remedy. See
the example policy and
the validation contract.
The default evidence directory is a fresh private temporary directory;
--evidence-dir selects its parent. Version 1 needs POSIX process groups.
Evidence corrections retain their requirement IDs. See evidence withdrawal for the restricted Spec-Withdraw contract.
The privacy identity contract distinguishes Git display names from strict local identifiers.
Run pipelines-hook no-legacy-concepts [ROOT ...] (or --root ROOT) to check tracked .py, .rs and .md files for CONCEPT:[A-Z]+-[0-9]. With no root it checks the current directory. The optional hook has the same ID. It does not change the eight blocking gates.
The original historical exclusions remain: check_no_legacy_markers.py, CHANGELOG.md, concept_map.md, concepts.yaml, and concept_reservations.yaml; directories __pycache__, .git, .venv, node_modules, target, build, and dist are not scanned. Untracked and ignored output is not input. Each target uses its own index, including from foreign worktrees or Git hooks. Findings include relative paths and line numbers and exit one. Unreadable input or an unavailable Git repository exits two, never a clean verdict.
pipelines-hook crates-io-only fails a Rust dependency that does not come from crates.io. pipelines-hook rustfmt-scope [--base REF] [PATH...] runs a check-only rustfmt on the changed Rust files with the pinned toolchain ([tool.pipelines_hooks.rustfmt_scope]). pipelines-hook cargo-advisories cross-checks the cargo-deny ignore list with a dated acceptance ledger and runs the pinned cargo deny check advisories ([tool.pipelines_hooks.cargo_advisories]).
pipelines-hook lockfile-version-mirrors --root ROOT checks the self-package
version in uv.lock and direct dependency pins/ranges in requirements.txt
against pyproject.toml. Missing artifacts and editable self entries without a
version are valid. Dynamic versions fall back to .bumpversion.cfg.
This is not uv-lock-check, which runs uv lock --check.
pipelines-hook coupling --root ROOT checks static Python imports in tracked
consumer packages. ROOT must be the consumer repository root (default: current
directory), not a package or sibling path. The consumer declares its policy:
[tool.pipelines_hooks]
packages = ["geniusbot"]
[tool.pipelines_hooks.coupling]
forbidden = ["agent_utilities", "epistemic_graph", "arbora", "agent_terminal_ui", "agent_webui"]
allowed = ["arbora.client"]This example follows the ecosystem boundary.
No adapter file exemption applies. Module prefixes match at dot boundaries;
from arbora import client is allowed, but importing the parent itself is not.
Missing configuration, package source, invalid Python or Git errors refuse with
exit two; prohibited static imports fail with exit one. The scanner uses the
consumer's own tracked index, ignores untracked build output, and does not
claim dynamic import or runtime isolation coverage.
An optional ordered layers list, most stable first, adds the import-direction
check (PIPE-GATES-R088). Each entry is a unit (<package>.<first segment>),
or a list of peer units that share a rank; name the package itself to layer its
root __init__. Relative imports are resolved. An import from an earlier layer
to a later one, an import of a private module (a leading-underscore segment) of
another unit, a unit named in no layer, and an import cycle among units are
located findings; duplicate or malformed entries refuse with exit two. With
layers set, forbidden is optional.
[tool.pipelines_hooks.coupling]
layers = ["pkg.types", ["pkg.core", "pkg.store"], "pkg.api", "pkg"]pipelines-hook environment-config-only --root ROOT (PIPE-GATES-R089) is
opt-in for repositories that declare [tool.pipelines_hooks.environment_config_only] declared = true. A tracked script (executable bit or shebang, outside tests)
must be a thin wrapper: at most 12 code lines, no loop, conditional, function
or short-circuit. Anything else is a finding naming the file; there is no
allowance list. Both hooks are opt-in and
do not alter the eight-gate landing partition.
pipelines-hook state-machine-shape (pre-commit and pre-push) and
pipelines-hook state-machine-census (manual) report implicit state machines
in Python and Rust: a status assigned outside the one transition function, a
string status, lifecycle flags, a declared table without edge and refusal
tests, an enum status with no declared machine, and a table whose
states-by-events matrix fails a reachability, dead-end, absorbing, determinism
or refusal-test check. Each finding is <file>:<line> <type> <code> rule <n>.
The consumer declares its sources:
[tool.pipelines_hooks.state_machine]
paths = ["src"]
exclude = ["**/fixtures/**"]
languages = ["python", "rust"]
mode = "staged" # print findings and exit 0; "blocking" exits 1pipelines-hook secret-fields [--probe] (pre-push, manual) checks that a
connector serves no credential material: unmarked secret-named fields of
response models and package schemas, the output schemas of the served tools
(--probe builds the server), and credential-shaped values in recorded outputs
under tests/. A field is marked with SecretStr, Field(exclude=True),
x-redacted, writeOnly, or x-secret-bearing for a newly minted secret that
a tool returns once. [tool.pipelines_hooks.secret_fields] mode is staged
(print, exit 0) or blocking.