From 2859f7d936733db841c592608f0a9f78320a35d3 Mon Sep 17 00:00:00 2001 From: "@conglomerate-dipper.test.pds.did.bot" Date: Mon, 26 Oct 2026 14:25:40 -0400 Subject: [PATCH] build: add a prek config matching the owner's other repos Wires file hygiene, cargo fmt and clippy, node --check, py_compile, committed and the change-id hook, so `prek install` from AGENTS.md has a config to read. Co-Authored-By: Claude Opus 5.5 Change-Id: I6b73a6accb9d58f4da10d981d2a9f4439bf61f82 --- committed.toml | 51 +++++++++++++++++++++++++++++++ prek.toml | 63 +++++++++++++++++++++++++++++++++++++++ scripts/change-id-hook.sh | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 committed.toml create mode 100644 prek.toml create mode 100755 scripts/change-id-hook.sh diff --git a/committed.toml b/committed.toml new file mode 100644 index 0000000..66ebdb2 --- /dev/null +++ b/committed.toml @@ -0,0 +1,51 @@ +# Rules for `committed` (https://github.com/crate-ci/committed), run as a +# commit-msg hook from prek.toml. +# +# Most lines below override a default. The overrides are what make the linter +# describe the style this project actually writes, rather than rejecting it. + +style = "conventional" + +# committed capitalizes the subject by default, which for a conventional commit +# means requiring `feat(og): Make the card the link preview`. This project +# writes the description in lower case. +subject_capitalized = false + +# The defaults are 50 columns of subject and 72 of any wrapped line. The +# conventional `type(scope)!: ` prefix eats ten to twenty columns before the +# description even starts, which is most of why 50 does not fit. +subject_length = 80 +line_length = 80 + +# The default list is fix, feat, chore, docs, style, refactor, perf, test. +# `build`, `ci` and `revert` complete the conventional set. +allowed_types = [ + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "build", + "ci", + "chore", + "revert", +] + +# Left at their defaults, deliberately: +# +# allowed_scopes — unset, so any scope passes. The scopes here track the module +# layout, which is still moving. Pinning the list would mean a rename could +# only land by editing this file first. +# +# imperative_subject — true, and a wordlist heuristic, not grammar. It rejects +# a description opening with an article (`fix: a fix for it`) and some +# inflected verbs (`adds`, `added`). If it ever rejects a subject that is +# genuinely imperative, that is the bug, and this is the knob. +# +# subject_not_punctuated — true. A trailing full stop is rejected; inline +# punctuation is fine. +# +# no_wip, no_fixup — true. Both are aimed at a branch that is about to be +# rebased anyway, and this project rebases. diff --git a/prek.toml b/prek.toml new file mode 100644 index 0000000..c9a147a --- /dev/null +++ b/prek.toml @@ -0,0 +1,63 @@ +# Hooks for prek (https://prek.j178.dev/). Run `prek install` once. + +# `prek install` wires the pre-commit hook and nothing else unless it is told +# otherwise, so without this line the commit-msg hooks below would sit in the +# file, installed by nobody and firing never. +default_install_hook_types = ["pre-commit", "commit-msg"] + +# `stages` appears on every hook because there is more than one stage in play. +# A hook that does not name its stages runs in all of them, so without this the +# file-hygiene and cargo hooks would also fire on commit-msg, where the only +# path they are handed is .git/COMMIT_EDITMSG. The one hook deliberately without +# a `stages` here is committed, further down, which declares `commit-msg` in its +# own manifest. A top-level `default_stages` would override that and turn it off. +# +# web/fonts/OFL.txt is excluded from the fixers because it is other people's +# licence text, reproduced as its terms require. +[[repos]] +repo = "https://github.com/pre-commit/pre-commit-hooks" +rev = "v6.0.0" +hooks = [ + { id = "check-merge-conflict", stages = ["pre-commit"] }, + { id = "check-added-large-files", stages = ["pre-commit"] }, + { id = "check-yaml", stages = ["pre-commit"] }, + { id = "check-toml", stages = ["pre-commit"] }, + { id = "check-json", stages = ["pre-commit"] }, + { id = "mixed-line-ending", args = ["--fix=lf"], stages = ["pre-commit"], exclude = '^web/fonts/' }, + { id = "end-of-file-fixer", stages = ["pre-commit"], exclude = '^web/fonts/' }, + { id = "trailing-whitespace", stages = ["pre-commit"], exclude = '^web/fonts/' }, +] + +# Run the project's own toolchain rather than a pinned copy of it. The cargo +# hooks act on the whole crate, so they take no filenames and run at most once +# per commit; `files` only decides whether they run at all. These are the +# Verification commands in AGENTS.md. +[[repos]] +repo = "local" +hooks = [ + { id = "cargo-fmt", name = "cargo fmt", entry = "cargo fmt --all -- --check", language = "system", types = ["file"], files = '\.rs$', pass_filenames = false, stages = ["pre-commit"] }, + # clippy type-checks as it lints, so there is no separate cargo check hook. + # `aws` is the crate's only feature; without it the S3 code is not compiled. + { id = "cargo-clippy", name = "cargo clippy", entry = "cargo clippy --all-targets --features aws -- -D warnings", language = "system", types = ["file"], files = '(\.rs$|Cargo\.(toml|lock)$)', pass_filenames = false, stages = ["pre-commit"] }, + # `node --check a.js b.js` checks only `a.js`, so each file is handed over on + # its own. + { id = "node-check", name = "node --check", entry = "bash -c 'for f in \"$@\"; do node --check \"$f\" || exit 1; done' _", language = "system", files = '^web/.*\.js$', stages = ["pre-commit"] }, + { id = "py-compile", name = "py_compile", entry = "python3 -m py_compile", language = "system", files = '^scripts/.*\.py$', stages = ["pre-commit"] }, +] + +# Conventional Commits, checked as the message is written. The rules are in +# committed.toml. +[[repos]] +repo = "https://github.com/crate-ci/committed" +rev = "v1.1.11" +hooks = [{ id = "committed" }] + +# Stamp a Change-Id trailer on commits that lack one: the identity a stacked +# pull request is matched to its commits by. Added by `atgc repo configure +# --hook-config`; the script is atgc's, and does nothing to a commit that has +# an id already. +[[repos]] +repo = "local" +hooks = [ + { id = "change-id", name = "change-id", entry = "scripts/change-id-hook.sh", language = "system", stages = ["commit-msg"], always_run = true }, +] diff --git a/scripts/change-id-hook.sh b/scripts/change-id-hook.sh new file mode 100755 index 0000000..58cd34f --- /dev/null +++ b/scripts/change-id-hook.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# atgc-change-id-hook v1 +# +# Stamp a `Change-Id:` trailer on a commit that has none. The id is what a +# stacked pull request is matched to its commits by, and it is the one thing +# about a commit that survives an amend or a rebase — so it has to be written +# when the commit is, not bolted on afterwards by rewriting the branch. +# +# Installed by `atgc repo configure`. Deleting this file is a supported way +# to turn it off; atgc reinstalls it only when the slot is empty. +# +# Nothing here prompts, and nothing here fails a commit: every path that +# cannot produce an id leaves the message exactly as it found it and exits 0. + +msg="$1" +[ -n "$msg" ] || exit 0 +[ -f "$msg" ] || exit 0 + +# Already carries one. Also covers a commit being amended, whose trailer is +# already in the message being re-edited. +if grep -qi '^Change-Id:[[:space:]]*I' "$msg"; then + exit 0 +fi + +# A fixup or squash belongs to the commit it names, and will be folded into +# it: an id of its own would be one more id than there are changes. +if head -n 1 "$msg" | grep -qE '^(fixup|squash|amend)! '; then + exit 0 +fi + +# An empty message aborts the commit. Stamping one would turn that abort into +# a commit whose entire message is a trailer. +if [ -z "$(sed -e 's/#.*//' -e '/^[[:space:]]*$/d' "$msg")" ]; then + exit 0 +fi + +# Unique, not reproducible: author, time, pid and the message itself. Cut to +# the 40 hex digits a change-id is written with, since a repository using +# sha256 hashes would otherwise hand back 64. +id=$( + { + git var GIT_AUTHOR_IDENT 2>/dev/null + echo "$$" + date +%s 2>/dev/null + cat "$msg" + } | git hash-object --stdin 2>/dev/null | cut -c1-40 +) +[ -n "$id" ] || exit 0 + +# `interpret-trailers` places it in the trailer block, after a Signed-off-by +# and before nothing, and knows about comments and the scissors line — all +# the reasons not to append a line by hand here. +git interpret-trailers --if-exists doNothing --trailer "Change-Id: I$id" --in-place "$msg" 2>/dev/null + +exit 0 -- knotmirror 0.0.1