From 0deace0907c502f56a9c8b254ac1533341537106 Mon Sep 17 00:00:00 2001 From: Zack Maril Date: Sun, 30 Aug 2026 06:18:42 -0400 Subject: [PATCH] chore: take the fleet's managed files Written by conf and distributed by ordnung fleet sync, not edited here: the committed Git hooks, the prose and stylesheet configuration, and the current lint workflow. A local change to any of them is drift the next sync reports. The hooks arrive executable, which they have to: Git silently declines to run a hook without its mode, and a hook that never runs looks exactly like one that passes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0144PU3MssnfbM5tZiqXW7d3 --- .githooks/README.md | 29 ++++++++++++++ .githooks/commit-msg | 26 +++++++++++++ .githooks/pre-commit | 9 +++++ .githooks/run-straitjacket | 27 +++++++++++++ .github/workflows/fleet-lint.yml | 67 ++++++++++++++++++++++++++++++++ .vale.ini | 16 ++++++++ 6 files changed, 174 insertions(+) create mode 100644 .githooks/README.md create mode 100755 .githooks/commit-msg create mode 100755 .githooks/pre-commit create mode 100755 .githooks/run-straitjacket create mode 100644 .vale.ini diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 0000000..da236b2 --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,29 @@ +# .githooks + +Committed Git hooks that run the fleet's gate locally, before a commit lands, +so a failure surfaces here rather than after a push. + +Fleet-managed by [conf](https://github.com/PowderworksCode/conf); edit them +there, not here — a local change is drift the next sync reports. + +## Activate + +Hooks are not enabled by a clone. The repository's development script does it, +or run it once yourself: + +```sh +git config core.hooksPath .githooks +``` + +## What runs + +- `commit-msg` enforces Conventional Commits on the subject line. +- `pre-commit` runs Straitjacket over the tree, through `run-straitjacket`. + +`run-straitjacket` **fails** when Straitjacket is not installed. A hook that +skips its only check wherever the tool is missing reports "clean" most loudly +where it has looked least; the message says how to install it. + +## Bypass + +`git commit --no-verify` skips the hooks for one commit. diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..9b1b482 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,26 @@ +#!/bin/sh +# Conventional-commit gate. The fleet lints pull-request titles in CI; this +# applies the same rule to the commit subject, so a bad message fails here +# rather than after a push. Bypass with `git commit --no-verify`. +# +# Fleet-managed by conf (.ordnung/managed/githooks/commit-msg): edit it there. +msg_file="$1" + +# First non-blank, non-comment line is the subject. +subject=$(grep -vE '^[[:space:]]*#' "$msg_file" | grep -vE '^[[:space:]]*$' | head -n1) + +# Git's own machine-generated messages pass untouched. +case "$subject" in + "Merge "*|"Revert "*|"fixup! "*|"squash! "*) exit 0 ;; +esac + +if printf '%s' "$subject" | + grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9./_-]+\))?!?: .+'; then + exit 0 +fi + +echo "commit message must follow Conventional Commits: type(scope): summary" >&2 +echo " types: feat fix docs style refactor perf test build ci chore revert" >&2 +echo " got: $subject" >&2 +echo " see https://www.conventionalcommits.org" >&2 +exit 1 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..6a8c345 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,9 @@ +#!/bin/sh +# The local gate: what CI would say about this tree, said before the commit +# lands. Bypass with `git commit --no-verify`. +# +# Fleet-managed by conf (.ordnung/managed/githooks/pre-commit). +set -eu + +hooks_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +exec "$hooks_dir/run-straitjacket" . diff --git a/.githooks/run-straitjacket b/.githooks/run-straitjacket new file mode 100755 index 0000000..94ed99e --- /dev/null +++ b/.githooks/run-straitjacket @@ -0,0 +1,27 @@ +#!/bin/sh +# Run Straitjacket over the repository, the way CI does. +# +# It fails when Straitjacket is not installed rather than passing quietly: a +# hook that skips its only check on the machines that lack the tool is a hook +# that reports "clean" most loudly where it has looked least. +# +# Fleet-managed by conf (.ordnung/managed/githooks/run-straitjacket). +set -eu + +if command -v straitjacket >/dev/null 2>&1; then + exec straitjacket "$@" +fi + +# A local install that is not on PATH is still an install. +for candidate in "$HOME/.local/bin/straitjacket" "$HOME/.cargo/bin/straitjacket"; do + if [ -x "$candidate" ]; then + exec "$candidate" "$@" + fi +done + +echo "straitjacket is not installed, and this hook will not pass without it." >&2 +echo "" >&2 +echo " curl -fsSL https://straitjacket.dev/install | sh" >&2 +echo "" >&2 +echo "Then re-run the commit. To skip the hooks once: git commit --no-verify" >&2 +exit 1 diff --git a/.github/workflows/fleet-lint.yml b/.github/workflows/fleet-lint.yml index 87e2df6..83f07d3 100644 --- a/.github/workflows/fleet-lint.yml +++ b/.github/workflows/fleet-lint.yml @@ -84,6 +84,73 @@ jobs: min-severity: high advanced-security: false + # Prose style, over the Markdown a member writes. Vale fetches the packages + # named in .vale.ini at run time, so the styles are versioned by that file + # rather than committed here. + prose-style: + name: vale + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # Asked after checkout, never as a job-level `if: hashFiles(...)`: that + # reads a workspace which does not exist yet, and a workflow whose + # expression cannot be evaluated does not start at all. + - name: A configured member is a graded one + id: configured + run: | + if [ -f .vale.ini ]; then + echo "vale=true" >> "$GITHUB_OUTPUT" + else + echo "vale=false" >> "$GITHUB_OUTPUT" + echo "no .vale.ini; prose style is not graded here" + fi + - uses: errata-ai/vale-action@518a9136acc6e6668ce7c00d367051e0941e87ff # v3.0.0 + if: steps.configured.outputs.vale == 'true' + with: + fail_on_error: true + + # Stylesheets, for the members that configure them. Run through bunx rather + # than a dependency, so a repository that writes CSS is not made to declare a + # linter it never imports. + styles: + name: stylelint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: "1.4.0" + - name: A configured member is a graded one + id: configured + run: | + if [ -f .stylelintrc.json ]; then + echo "stylelint=true" >> "$GITHUB_OUTPUT" + else + echo "stylelint=false" >> "$GITHUB_OUTPUT" + echo "no .stylelintrc.json; stylesheets are not graded here" + fi + - name: Stylelint + if: steps.configured.outputs.stylelint == 'true' + run: | + sheets=$(git ls-files '*.css' '*.scss' | grep -v node_modules || true) + if [ -z "$sheets" ]; then + echo "no stylesheets tracked" + exit 0 + fi + # stylelint-config-standard extends stylelint-config-recommended, so + # both have to be resolvable from wherever stylelint runs — `bunx + # stylelint` alone brings neither, and fails on the extends rather + # than on the CSS. They go in a scratch directory, and + # --config-basedir points the resolution there, so a repository that + # writes CSS still does not declare a linter it never imports. + tools=$(mktemp -d) + (cd "$tools" && bun add --silent stylelint@17.14.1 stylelint-config-standard@40.0.0) + # shellcheck disable=SC2086 — the file list is deliberately split. + "$tools/node_modules/.bin/stylelint" \ + --config .stylelintrc.json --config-basedir "$tools" $sheets + shell: name: shellcheck runs-on: ubuntu-latest diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000..0142431 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,16 @@ +# Fleet-managed by conf (.ordnung/managed/vale/vale.ini). +# +# proselint and write-good rather than a house style guide: they catch prose +# that is weak on anyone's terms — weasel words, passive constructions, +# clichés, redundancy — without imposing a voice on repositories whose readers +# are each other. A style guide can come later, from writing that exists. +StylesPath = .vale/styles +MinAlertLevel = warning + +Packages = proselint, write-good + +[*.md] +BasedOnStyles = proselint, write-good + +# Prose lives in sentences; a fenced block is code someone will run. +BlockIgnores = (?s) *(```.*?```)