Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ inherit configs and policies via the mechanisms below.
| Renovate `extends` | `renovate.json` in each repo: `extends: github>JacobPEvans/.github:renovate-presets` (this repo's `renovate.json` is the example) |
| Biome config | Each repo carries a copy of `biome.jsonc` scaffolded from this repo; Renovate keeps it in sync |
| markdownlint config | Each repo carries a copy of `.markdownlint-cli2.yaml` from this repo; sync TBD (manual for now) |
| Pre-commit hooks (shared) | `precommit/` — Nix flake import or static YAML copy; see [`precommit/README.md`](precommit/README.md) |
| AI assistant policy | `CLAUDE.md` — read by Claude Code on every session |

## Usage
Expand Down Expand Up @@ -101,6 +102,8 @@ This repo exposes the following inheritance surfaces:
| `biome.jsonc` | Canonical Biome lint + format config (code) |
| `.markdownlint-cli2.yaml` | Canonical markdownlint-cli2 config (`.md` files) |
| `renovate.json` | Org-default Renovate extending JacobPEvans presets |
| `precommit/` | Shared pre-commit layer (canonical lint configs + static YAML templates); see [`precommit/README.md`](precommit/README.md) |
| `zizmor.yml` | Org-wide zizmor workflow-security policy (referenced by the pre-commit `zizmor` hook) |
| `SECURITY.md` | Org-wide vulnerability reporting policy (auto-applied to every dryvist repo's Security tab) |
| `profile/README.md` | Org profile page at <https://github.com/dryvist> |

Expand Down
176 changes: 176 additions & 0 deletions precommit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# precommit — org-wide pre-commit shared layer

Single source of truth for pre-commit hook configuration across every
dryvist repo. Two consumption paths share these artifacts.

- **Nix flake module** — Repos with `flake.nix` + `.envrc` (terraform-*,
ansible-*, nix-*, orbstack-kubernetes) consume
`inputs.nix-devenv.flakeModules.{base,terraform,ansible,python,nix,markdown}`.
Versions pinned via `flake.lock`.
- **Static YAML copy** — Repos without Nix (cribl packs, raw CI
checkouts, external contributor clones) copy `templates/<profile>.yaml`
at scaffold time. Renovate keeps `rev:` pins fresh.

Both paths reference the same canonical config files in `configs/` for
tools that demand a config file on disk (ansible-lint, yamllint, tflint).
Markdown lint config lives at the dryvist/.github root
(`../.markdownlint-cli2.yaml`) for backwards compatibility with existing
markdownlint workflow consumers.

## Layout

```text
precommit/
├── configs/
│ ├── ansible-lint.yml # canonical .ansible-lint
│ ├── tflint.hcl # canonical .tflint.hcl
│ └── yamllint.yml # canonical .yamllint.yml
├── templates/
│ ├── base.yaml # common 80% (no language-specific hooks)
│ ├── terraform.yaml # base + terraform_fmt/validate/tflint/docs
│ ├── ansible.yaml # base + ansible-lint + yamllint
│ └── python.yaml # base + ruff + ruff-format + mypy
└── README.md
```

The markdownlint canonical (`.markdownlint-cli2.yaml`) stays at the
repo root — it predates this directory and is referenced by existing
consumer workflows. New canonical configs land here in `configs/`.

## Installation

This directory is consumed by reference, not installed. Pick the path
that matches the consumer repo once.

### Add to a Nix-flake repo

No copy step needed. Add the `nix-devenv` flake input and one
`imports = [ ... ]` line in the consumer's `flake.nix` (see Usage).

### Add to a non-Nix repo

Fetch the matching template at scaffold time, plus the configs the
hooks need:

```bash
# Pick base / terraform / ansible / python
gh api repos/dryvist/.github/contents/precommit/templates/terraform.yaml \
-H "Accept: application/vnd.github.raw" > .pre-commit-config.yaml

# Materialize tflint canonical (terraform profile only)
gh api repos/dryvist/.github/contents/precommit/configs/tflint.hcl \
-H "Accept: application/vnd.github.raw" > .tflint.hcl

# Markdown lint config lives at the .github root
gh api repos/dryvist/.github/contents/.markdownlint-cli2.yaml \
-H "Accept: application/vnd.github.raw" > .markdownlint-cli2.yaml

# Zizmor policy for workflow security
gh api repos/dryvist/.github/contents/zizmor.yml \
-H "Accept: application/vnd.github.raw" > zizmor.yml

pre-commit install
```

For `ansible.yaml`, also fetch `precommit/configs/ansible-lint.yml`
to `.ansible-lint` and `precommit/configs/yamllint.yml` to
`.yamllint.yml`. For `python.yaml`, no extra configs needed beyond
optional repo-local `pyproject.toml` for mypy / ruff.

## Usage

### Run hooks in a Nix-flake repo

In the consumer's `flake.nix`:

```nix
{
inputs.nix-devenv.url = "github:dryvist/nix-devenv";
inputs.flake-parts.url = "github:hercules-ci/flake-parts";

outputs = inputs@{ flake-parts, nix-devenv, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "aarch64-darwin" "x86_64-linux" ];
imports = [ inputs.nix-devenv.flakeModules.terraform ];
};
}
```

Profile picks one of `{base, terraform, ansible, python, nix, markdown}`.
The module imports `dev-hygiene` (base hooks) and layers the matching
language hooks on top. No `.pre-commit-config.yaml` needed in the repo.

`base`, `nix`, and `markdown` are aliases for `dev-hygiene` — the base
already covers Nix lints (deadnix, statix) and markdownlint via
file-glob filtering. Repos pick one of those aliases for readability;
the runtime behavior is identical.

### Run hooks in a non-Nix repo

After running the Installation step above, hooks run automatically on
`git commit`. To run them ad-hoc:

```bash
pre-commit run --all-files
```

Renovate's custom manager in dryvist/.github keeps `rev:` pins in the
copied `.pre-commit-config.yaml` fresh going forward. Sync of the
config files themselves (`.tflint.hcl`, etc.) is still manual today —
extending the Renovate custom manager to cover them is tracked
separately.

## API

This directory exposes the following surfaces:

| Path | Purpose |
| --- | --- |
| `configs/ansible-lint.yml` | Canonical ansible-lint (production profile, fqcn + no-changed-when) |
| `configs/tflint.hcl` | Canonical tflint (terraform plugin, recommended preset, docs rules) |
| `configs/yamllint.yml` | Canonical yamllint (line-length 160 warn, octal forbidden, ansible-lint-compatible) |
| `templates/base.yaml` | Common-80% static `.pre-commit-config.yaml` for non-Nix consumers |
| `templates/terraform.yaml` | `base` plus terraform_fmt / validate / tflint / docs |
| `templates/ansible.yaml` | `base` plus ansible-lint + yamllint |
| `templates/python.yaml` | `base` plus ruff / ruff-format / mypy |

## Contributing

Adding a new profile means both paths stay in sync:

1. **Nix side** — add `flake-modules/profiles/<name>.nix` in
[`dryvist/nix-devenv`](https://github.com/dryvist/nix-devenv) and
expose `flakeModules.<name>` in its `flake.nix`. Validate with a
smoke-test consumer under `tests/profile-modules/<name>/`.
2. **YAML side** — add `templates/<name>.yaml` here that mirrors the
Nix profile's hook set.
3. Document both paths in this README's `Layout` section.

Profile hooks must stay in sync between the two paths. The Nix smoke
tests in `dryvist/nix-devenv` catch hook-name drift in git-hooks.nix;
no equivalent test exists for the YAML templates yet (a `pre-commit
dry-run` against a representative repo is the manual check).

### Canonical-config choices

- `configs/` holds files that tools demand on disk. Tools that accept
all config via CLI args ship their canonical at the repo root (see
`.markdownlint-cli2.yaml`).
- Each merged config picks the **most-common variant** from the
inventory rather than the strictest superset, to minimize migration
churn. Consumers add stricter rules locally until a critical mass
agrees to lift them into the canonical.
- AWS / GCP / Azure tflint plugins stay opt-in per repo. Their
rulesets are large and noisy on repos that don't target that cloud.
The canonical `tflint.hcl` enables only the core terraform plugin.
- `bandit` and `detect-secrets` appear in one inventory repo each and
are NOT in the python template. Repos that want them add a `repo:`
block locally.
- `checkov` for terraform appears in three repos and is NOT in the
terraform template. Run time dominates the hook cycle; consumers
opt in locally via `pre-commit.settings.hooks.checkov.enable = true;`
(Nix path) or a `repo:` block (YAML path).

## License

[Apache-2.0](../LICENSE) — inherited from the dryvist/.github root.
44 changes: 44 additions & 0 deletions precommit/configs/ansible-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
# dryvist organization-wide ansible-lint canonical.
#
# Single source of truth for ansible-lint across every dryvist ansible
# repo. Consumer repos either fetch this file at scaffold time (non-Nix
# path) or have the Nix-side `fetch-shared-configs` helper materialize
# it at devShell entry (Nix path).
#
# Schema: https://ansible.readthedocs.io/projects/lint/configuring/

profile: production

exclude_paths:
- .git/
- .github/
- .cache/
- .venv/
- venv/
# SOPS-encrypted files are opaque ciphertext + metadata; linting them
# is meaningless. The recursive glob also catches future encrypted
# files in any subdirectory.
- "**/*.enc.yaml"
- "**/*.enc.yml"

# Noise from yaml line-length is handled by .yamllint's line-length rule
# at warning level; ansible-lint should not duplicate that complaint.
skip_list:
- yaml[line-length]

warn_list:
- experimental

enable_list:
- fqcn
- no-changed-when

# File-type overrides for files in non-standard locations. Repos that
# don't have these files are unaffected — ansible-lint ignores absent
# entries.
kinds:
- playbook: "**/load_terraform.yml"

use_default_rules: true
offline: false
43 changes: 43 additions & 0 deletions precommit/configs/tflint.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# dryvist organization-wide tflint canonical.
#
# Single source of truth for tflint configuration across every dryvist
# terraform/opentofu repo. Consumer repos either fetch this file at
# scaffold time (non-Nix path) or have the Nix-side `fetch-shared-configs`
# helper materialize it into the repo at devShell entry (Nix path).
#
# The terraform plugin is enabled with the `recommended` preset. The
# rules below are an explicit superset of the most-common per-repo
# variants (terraform-proxmox / terraform-github style): documented
# variables/outputs, required providers/version. AWS / GCP / Azure
# plugins stay opt-in per repo because they pull large rulesets and
# slow tflint considerably on repos that don't target that cloud.
#
# Schema: https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/config.md

config {
format = "compact"
call_module_type = "local"
force = false
}

plugin "terraform" {
enabled = true
preset = "recommended"
}

# Documentation-discipline rules (most-shared canonical).
rule "terraform_documented_variables" {
enabled = true
}

rule "terraform_documented_outputs" {
enabled = true
}

rule "terraform_required_providers" {
enabled = true
}

rule "terraform_required_version" {
enabled = true
}
54 changes: 54 additions & 0 deletions precommit/configs/yamllint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# dryvist organization-wide yamllint canonical.
#
# Single source of truth for yamllint across every dryvist repo with
# YAML content (which is most of them). Consumer repos either fetch
# this at scaffold time (non-Nix path) or have the Nix-side
# `fetch-shared-configs` helper materialize it at devShell entry
# (Nix path).
#
# Rule choices represent the most-common variant across the inventory
# (ansible-proxmox style). Repos that want a different line-length or
# stricter truthy rules can override locally.
#
# Schema: https://yamllint.readthedocs.io/en/stable/configuration.html

extends: default

ignore: |
.github/aw/
.github/workflows/*.lock.yml
.github/workflows/agentics-maintenance.yml
# SOPS-encrypted files: opaque ciphertext; the recursive glob also
# catches future encrypted files in any subdirectory.
**/*.enc.yaml
**/*.enc.yml

rules:
line-length:
max: 160
level: warning
truthy:
allowed-values:
- "true"
- "false"
- "yes"
- "no"
check-keys: false
comments:
min-spaces-from-content: 1
# Required by ansible-lint compatibility — ansible playbooks frequently
# put comments at the same indent as the surrounding content.
comments-indentation: false
braces:
min-spaces-inside: 0
max-spaces-inside: 1
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
indentation:
spaces: 2
indent-sequences: true
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
56 changes: 56 additions & 0 deletions precommit/templates/ansible.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# dryvist ansible pre-commit template (non-Nix consumers).
#
# Includes the base hook set plus ansible-lint + yamllint. Both consume
# canonical configs from dryvist/.github at precommit/configs/, which
# the consumer materializes alongside .pre-commit-config.yaml:
#
# gh api repos/dryvist/.github/contents/precommit/configs/ansible-lint.yml \
# -H "Accept: application/vnd.github.raw" > .ansible-lint
# gh api repos/dryvist/.github/contents/precommit/configs/yamllint.yml \
# -H "Accept: application/vnd.github.raw" > .yamllint.yml
#
# Nix-flake consumers should NOT use this file. They get equivalent
# coverage via `imports = [ inputs.nix-devenv.flakeModules.ansible ];`.
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-json
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=500]
- id: end-of-file-fixer
- id: trim-trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: detect-private-key

- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.22.1
hooks:
- id: markdownlint-cli2
exclude: ^CHANGELOG\.md$

- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.25.2
hooks:
- id: zizmor
files: ^\.github/workflows/.*\.ya?ml$
args:
- --persona=regular
- --min-severity=medium
- --min-confidence=medium
- --config
- zizmor.yml

- repo: https://github.com/ansible/ansible-lint
rev: v26.4.0
hooks:
- id: ansible-lint

- repo: https://github.com/adrienverge/yamllint
rev: v1.38.0
hooks:
- id: yamllint
Loading