git-workflows/skills/pre-commit-architecture/SKILL.md
Use when adding or editing .pre-commit-config.yaml, wiring pre-commit hooks into a repo, scaffolding a new repo's lint/hook setup, or deciding where a hook or shared lint config should live. Covers the canonical nix-devenv/dryvist-.github architecture, profiles, and consumer patterns.
npx skillsauth add jacobpevans/claude-code-plugins pre-commit-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Single canonical home per artifact. No per-repo duplication of hook definitions or shared lint configs.
| Artifact | Canonical home | How consumers pull it |
| --- | --- | --- |
| Pre-commit hook definitions (Nix consumers) | dryvist/nix-devenv — lib/pre-commit-hooks.nix + flake-modules/profiles/<name>.nix | imports = [ inputs.nix-devenv.flakeModules.<profile> ] in the consumer's flake.nix |
| Pre-commit hook definitions (non-Nix consumers) | dryvist/.github precommit/templates/<profile>.yaml | Copy at scaffold; Renovate keeps rev: pins fresh |
| Shared lint configs (.markdownlint-cli2.yaml, .tflint.hcl, .ansible-lint, .yamllint.yml) | dryvist/.github — .markdownlint-cli2.yaml at root for backward compat; the rest in precommit/configs/ | Nix path: nix-devenv.lib.fetch-shared-configs materializes nix-store paths; non-Nix path: copy at scaffold |
| zizmor.yml workflow-security policy | dryvist/.github at the root | Same as above — passed as --config by the base profile's zizmor hook |
nix-devenv exports six profile names. Pick one per consumer repo.
| Profile | Adds on top of base hygiene | Inventory match |
| --- | --- | --- |
| base | Nothing — exposes the base wiring (deadnix, statix, check-yaml/toml/json, check-merge-conflict, check-added-large-files=500k, end-of-file-fixer, trim-trailing-whitespace, detect-private-keys, markdownlint-cli2, zizmor, treefmt) | Generic repos |
| nix | Alias for base (deadnix and statix file-glob to .nix automatically) | nix-* repos |
| markdown | Alias for base (markdownlint-cli2 file-globs to .md automatically) | Markdown-heavy repos |
| terraform | terraform-format, terraform-validate, tflint | terraform-*, tofu-* |
| ansible | ansible-lint, yamllint | ansible-* |
| python | ruff, ruff-format, mypy | python-template, mlx-benchmarks, etc. |
The base wiring already covers Nix lints (deadnix, statix) and
markdown (markdownlint-cli2); git-hooks.nix's file-glob filters
make those hooks inert on repos that don't have the matching file
types. So base, nix, and markdown are deliberately identical
modules — the name signals intent to the reader, not behavior.
# flake.nix in a consumer repo
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-25.11-darwin";
flake-parts.url = "github:hercules-ci/flake-parts";
nix-devenv = {
url = "github:dryvist/nix-devenv";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{ flake-parts, nix-devenv, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"aarch64-darwin"
"x86_64-darwin"
"x86_64-linux"
"aarch64-linux"
];
imports = [ nix-devenv.flakeModules.terraform ];
perSystem =
{ system, ... }:
{
devShells.default = nix-devenv.devShells.${system}.terraform;
};
};
}
Scaffold a new repo with this layout via
nix flake init -t github:JacobPEvans/nix-devenv#with-hooks.
# Pick the matching profile template
gh api repos/dryvist/.github/contents/precommit/templates/terraform.yaml \
-H "Accept: application/vnd.github.raw" > .pre-commit-config.yaml
# Materialize the configs the hooks need
gh api repos/dryvist/.github/contents/precommit/configs/tflint.hcl \
-H "Accept: application/vnd.github.raw" > .tflint.hcl
gh api repos/dryvist/.github/contents/.markdownlint-cli2.yaml \
-H "Accept: application/vnd.github.raw" > .markdownlint-cli2.yaml
gh api repos/dryvist/.github/contents/zizmor.yml \
-H "Accept: application/vnd.github.raw" > zizmor.yml
pre-commit install
.pre-commit-config.yaml
files. If the canonical profile doesn't cover something, add it to
nix-devenv's base profile (in lib/pre-commit-hooks.nix) or the
matching profile (in flake-modules/profiles/<name>.nix), then pull
it through everywhere on the next nix flake update..markdownlint,
.tflint.hcl, .ansible-lint, .yamllint) into a new repo. Pull
them via the Nix path (fetch-shared-configs) or copy from
dryvist/.github at scaffold (non-Nix path).flake.nix + flake.lock,
modify .envrc, delete .pre-commit-config.yaml, delete the
duplicated lint config files that the canonical now covers.flake.lock (Nix path) or rev:
strings in the static template (non-Nix path). Do NOT pin versions
per-repo unless overriding a specific consumer.precommit/README.md in dryvist/.github
for the architecture rationale, canonical-config choices, and the
trade-offs behind why certain hooks (checkov, bandit, detect-secrets)
stay opt-in per repo.cachix/git-hooks.nix's built-in tflint wrapper drops args beyond
$1, so the terraform profile's --config <sharedConfigs.tflint>
plumbing doesn't reach tflint. Consumers either keep a synced copy of
the canonical .tflint.hcl in the repo (tflint's local-config
discovery still finds it) or override tflint.args to lib.mkForce [ ]. An upstream fix to the wrapper would let
fetch-shared-configs drive tflint config directly.terraform-validate hooks need network access to tofu init
external modules. nix flake check runs hooks in a sandboxed
environment without network. Repos with external module references
override terraform-validate.enable = lib.mkForce false for the
flake-check path and rely on CI's OIDC-authenticated
terragrunt validate to cover the check.gitleaks is not in cachix/git-hooks.nix's built-in hook set yet.
Consumers that want it wire it as a custom hook locally; a follow-up
adds it to the base profile.tools
Use when installing or choosing CLI tools in a Nix flake repo, editing flake.nix or home-manager config, or when tempted to pip/pipx/uv/brew/npm install anything. Tools come from the dev shell or nix shell — never ad-hoc package managers.
testing
Use when creating or editing GitHub Actions workflows that call reusable workflows (uses: OWNER/repo/.github/workflows/...) — org owner references must be the literal current org, and shared-CI homes are under dryvist.
testing
Check PR merge readiness, sync local repo, cleanup stale worktrees; optional cross-repo sweep and stale-branch prune modes
tools
Local rebase-merge workflow for pull requests with signed commits