From 59ecafecce5e0d7984addf8775f76a9fae12d73a Mon Sep 17 00:00:00 2001 From: Timothy Date: Sun, 12 Jul 2026 18:18:30 +0200 Subject: [PATCH] =?UTF-8?q?docs(process):=20#303=20fold=20review=20nits=20?= =?UTF-8?q?=E2=80=94=20H3=20case-insensitive,=20granularity=20wording,=20n?= =?UTF-8?q?ewline=20note=20[decisions-edit]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversarial review (PR #305, MERGEABLE) nits: - H3: `grep -iE` so a root `Screenshot.PNG` is caught too (was lowercase-only). - decisions-guard.sh: comment the trailing-newline assumption (dropping the final newline would make git render the next append as a last-line modify -> false-block; self-correcting via [decisions-edit], .editorconfig enforces the newline). - docs: clarify CI is PR-wide (`range`) vs Husky per-commit (`staged`) — shared detection logic, deliberately different granularity; local hook is the stricter gate. Replaces the slightly-overstated "can't drift" wording. Touches the committed H9 decisions.md entry, hence the [decisions-edit] token. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/hooks/decisions-guard.sh | 4 ++++ .husky/pre-commit | 2 +- docs/ci-cd.md | 9 ++++++--- docs/decisions.md | 5 ++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.claude/hooks/decisions-guard.sh b/.claude/hooks/decisions-guard.sh index 98adb94cb..66ff68898 100755 --- a/.claude/hooks/decisions-guard.sh +++ b/.claude/hooks/decisions-guard.sh @@ -8,6 +8,10 @@ # Fail-open: any tooling trouble (unknown mode, non-numeric numstat, missing refs) -> allow. The point # is to catch the accidental rewrite-history case, never to wedge a legitimate commit. # +# Assumes decisions.md ends with a trailing newline (it does; .editorconfig enforces it). If that final +# newline were ever dropped, git would render the next append as a modify of the last line (deleted=1) +# and this would false-block the append until the author adds [decisions-edit] — cheap and self-correcting. +# # Modes: # staged pre-commit/commit-msg — staged diff vs HEAD; trailer read from # range CI (PR) — merge-base diff base...head; trailer scanned across base..head msgs diff --git a/.husky/pre-commit b/.husky/pre-commit index b0d9d357f..c5d5c66aa 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,7 +3,7 @@ cd .. # H3 (ersatztv#303) — never commit a screenshot dropped at the repo root. Belt-and-suspenders with # .gitignore (catches a forced `git add -f`). Root-level *.png only; nested paths are legit assets. -root_png=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^[^/]+\.png$' || true) +root_png=$(git diff --cached --name-only --diff-filter=ACM | grep -iE '^[^/]+\.png$' || true) if [ -n "$root_png" ]; then echo "husky - refusing to commit root-level screenshot(s):" printf ' %s\n' $root_png diff --git a/docs/ci-cd.md b/docs/ci-cd.md index b6613db1d..addddb592 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -122,9 +122,12 @@ Enforces the `docs/decisions.md` append-only convention (ersatztv#303 H9): fails merge-base diff **deletes or modifies** any existing line of that file, unless a commit in the range carries the `[decisions-edit]` token (for a factual fix or a documented supersession — see the `decisions.md` header). Pure insertions (a normal new entry: TOC line + appended block) pass. It runs -the same `.claude/hooks/decisions-guard.sh` the Husky `commit-msg` hook uses (`range` mode vs -`staged` mode), so local and CI enforcement share one implementation and can't drift. Like -`docs-reminder`, it's a seconds-long `git diff` with no dotnet/node setup (`runs-on: small`). +the same `.claude/hooks/decisions-guard.sh` the Husky `commit-msg` hook uses, so the *detection logic* +is shared and can't drift. Granularity differs, deliberately: Husky checks **each commit** (`staged` +mode, that commit's own message must carry the token); CI checks the **PR-wide** net diff (`range` mode, +accepts the token in *any* commit of the range). The local hook is therefore the stricter, primary +gate; CI is the backstop for direct pushes or bypassed hooks. Like `docs-reminder`, it's a +seconds-long `git diff` with no dotnet/node setup (`runs-on: small`). ## Dockerfile notes (`docker/Dockerfile`) diff --git a/docs/decisions.md b/docs/decisions.md index 473278ca1..bba9e3aa8 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -1344,7 +1344,10 @@ immune (no `bin/` on a fresh checkout). **This log is append-only by construction, not just by convention.** A commit or PR that deletes or modifies an existing line of `docs/decisions.md` is blocked — by the Husky `commit-msg` hook (`.claude/hooks/decisions-guard.sh staged`) locally and the blocking `decisions-guard` CI job (same -script, `range` mode) on PRs. Insertions anywhere are always allowed, so a normal new entry (TOC line +script, `range` mode) on PRs. Shared detection, deliberately different granularity: the Husky hook +gates **each commit** (its own message must carry the token); CI gates the **PR-wide** net diff +(token in any commit of the range suffices), so the local hook is the stricter primary gate and CI the +push/bypass backstop. Insertions anywhere are always allowed, so a normal new entry (TOC line near the top + a block appended at the bottom, both pure insertions) passes untouched. Detection is `git diff --numstat` deleted-count > 0, which is robust to markdown `-` list markers (a byte-level `-` prefix would false-match). The block is lifted only by the literal **`[decisions-edit]`** token in the