From 34f32183995c79cc339139f2997ad117936c8426 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:43:04 +0100 Subject: [PATCH] fix(hooks): fence bot-directives staged mode to .machine_readable Staged mode grepped every staged prose file for codex|gci|other-bot, while scan mode only checks .machine_readable/. Any README naming the AI tool "Codex" (e.g. 2-protocols/k9-coordination/README.adoc) could not be committed. Staged mode also skipped .deed/.a2ml, the very directive files scan mode checks. Fence staged mode to .machine_readable/ and add .deed/.a2ml to its extension list. New scripts/tests/validate-bot-directives-test.sh: 5/5 on this change, 2/5 against the previous hook (both defects reproduced). Owner ruling D316. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01D6JVLvmCeSFGcY6jSNesbL --- .githooks/validate-bot-directives.sh | 11 ++++- scripts/tests/validate-bot-directives-test.sh | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100755 scripts/tests/validate-bot-directives-test.sh diff --git a/.githooks/validate-bot-directives.sh b/.githooks/validate-bot-directives.sh index f8228ef70..f6673e117 100755 --- a/.githooks/validate-bot-directives.sh +++ b/.githooks/validate-bot-directives.sh @@ -17,13 +17,20 @@ validate_file() { fi } -# If staged files provided, only check those +# If staged files provided, only check those that sit under .machine_readable/, +# the same population scan mode checks below. Without this fence the grep ran +# over every staged prose file, so any README naming the AI tool "Codex" +# could not be committed (owner ruling D316). if [ -n "$STAGED_FILES" ]; then while IFS=$'\n' read -r file; do [ -z "$file" ] && continue + case "$file" in + .machine_readable/*|*/.machine_readable/*) ;; + *) continue ;; + esac # Check all text files case "$file" in - *.md|*.txt|*.adoc|*.yml|*.yaml|*.json|*.toml|*.sh|*.bash|*.js|*.ts|*.rs|*.ex|*.exs) ;; + *.a2ml|*.deed|*.md|*.txt|*.adoc|*.yml|*.yaml|*.json|*.toml|*.sh|*.bash|*.js|*.ts|*.rs|*.ex|*.exs) ;; *) continue ;; esac [ -f "$file" ] || continue diff --git a/scripts/tests/validate-bot-directives-test.sh b/scripts/tests/validate-bot-directives-test.sh new file mode 100755 index 000000000..d2d76b552 --- /dev/null +++ b/scripts/tests/validate-bot-directives-test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# Tests for .githooks/validate-bot-directives.sh in staged mode. +# +# Staged mode used to grep EVERY staged prose file for codex|gci|other-bot, +# while scan mode only looks under .machine_readable/. A README that names the +# AI tool "Codex" therefore could not be committed (owner ruling D316). These +# cases pin both halves: prose outside .machine_readable/ is not the gate's +# business, and a directive file inside it still fails (the planted positive +# control; without it a validator that checks nothing would pass every case). +set -uo pipefail +HOOK="$(cd "$(dirname "$0")/../.." && pwd)/.githooks/validate-bot-directives.sh" +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +pass=0; fail=0 + +# ck NAME EXPECTED_EXIT STAGED_PATH... — run the hook from $T in staged mode +# over the given relative paths and compare its exit code with EXPECTED_EXIT. +ck() { + local name="$1" want="$2" out rc; shift 2 + out="$(cd "$T" && INPUT_STAGED_FILES="$(printf '%s\n' "$@")" bash "$HOOK" 2>&1)"; rc=$? + if [ "$rc" = "$want" ]; then printf ' ok %s (exit %s)\n' "$name" "$rc"; pass=$((pass+1)) + else printf ' FAIL %s (expected exit %s, got %s) output=%s\n' "$name" "$want" "$rc" "${out:-}"; fail=$((fail+1)); fi +} + +mkdir -p "$T/docs" "$T/.machine_readable/bot_directives" "$T/sub/.machine_readable" +printf 'Agents: Claude, Gemini, Codex.\n' > "$T/docs/README.adoc" +printf '(bot codex)\n' > "$T/.machine_readable/bot_directives/legacy.deed" +printf 'gci = true\n' > "$T/sub/.machine_readable/x.a2ml" +printf '(bot claude)\n' > "$T/.machine_readable/bot_directives/ok.deed" + +ck "prose naming Codex outside .machine_readable passes" 0 docs/README.adoc +ck "deprecated directive in .machine_readable .deed fails" 1 .machine_readable/bot_directives/legacy.deed +ck "nested .machine_readable .a2ml directive fails" 1 sub/.machine_readable/x.a2ml +ck "clean directive file passes" 0 .machine_readable/bot_directives/ok.deed +ck "mixed set fails on the directive file alone" 1 docs/README.adoc .machine_readable/bot_directives/legacy.deed + +printf '%s passed, %s failed\n' "$pass" "$fail" +[ "$fail" -eq 0 ] && [ "$pass" -gt 0 ]