PureXML/.githooks/commit-msg
Mihaela Mihaljevic 708390242c chore: scaffold PureXML pure-Swift XML package
Root Swift package mirroring PureYAML's structure, rules, and verification
gates. Includes the XML node model (Node, Element, Attribute, QualifiedName),
a working emitter (Serializer) with escaping and pretty/compact options, and
structural validation. The parser surface is stable; parse() raises
notImplemented until the scanner lands, pinned by a test.

- dependency-free, namespaced under PureXML
- portability and style gates carried over (style, namespacing, forbidden
  patterns, swiftformat, swiftlint, linux, wasm, windows CI)
- 11 tests passing
2026-06-07 23:20:20 +02:00

127 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# commit-msg hook: reject style tells in commit messages.
#
# Enforces github-discipline.md Rule 5.1 (no AI attribution) and
# Rule 5.2 (no em dashes U+2014) at the commit boundary.
#
# Install (per repo): git config core.hooksPath .githooks
#
# Portable: uses only BSD-compatible grep (no -P, no \x escapes in patterns).
set -e
MSG_FILE="$1"
[ -z "$MSG_FILE" ] && exit 0
[ ! -f "$MSG_FILE" ] && exit 0
# Strip comment lines (lines starting with #) and the diff scissors block.
CONTENT=$(awk '
/^# ------------------------ >8 ------------------------$/ { exit }
/^[[:space:]]*#/ { next }
{ print }
' "$MSG_FILE")
FAIL=0
# Em dash U+2014 = UTF-8 bytes E2 80 94. Build the literal as a variable so the
# script source itself contains no em-dash character.
EMDASH=$(printf '\xe2\x80\x94')
if LC_ALL=C printf '%s' "$CONTENT" | grep -qF -- "$EMDASH"; then
printf 'commit-msg: forbidden em dash (U+2014) in commit message.\n' >&2
printf ' Replace with a comma, period, colon, semicolon, or restructure.\n' >&2
printf ' Rule: github-discipline.md Rule 5.2\n' >&2
printf ' Locations (line numbers in the cleaned message):\n' >&2
LC_ALL=C printf '%s\n' "$CONTENT" | grep -nF -- "$EMDASH" | sed 's/^/ /' >&2
FAIL=1
fi
# Rule 5.1: literal AI-attribution phrases.
LITERAL_PATTERNS=(
'Co-Authored-By: Claude'
'Co-authored-by: Claude'
'Generated with Claude Code'
'Generated with Claude'
'Generated by Claude'
'with help from Claude'
'Powered by Claude'
'powered by Claude'
'as an AI'
'As an AI'
'this commit was generated'
'This commit was generated'
'this change was generated'
'This change was generated'
)
for p in "${LITERAL_PATTERNS[@]}"; do
if printf '%s' "$CONTENT" | grep -qF -- "$p"; then
printf 'commit-msg: forbidden AI-attribution phrase: %s\n' "$p" >&2
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
FAIL=1
fi
done
# Rule 5.1: AI-tell signature emojis used as flourish at end of line.
# Build emoji byte sequences in a variable so the script source stays clean.
EMOJI_TELLS=$(printf '\xf0\x9f\xa4\x96\n\xe2\x9c\xa8\n\xf0\x9f\xaa\x84\n\xf0\x9f\xa7\xa0\n\xf0\x9f\xa6\xbe\n\xf0\x9f\xa4\x9d')
while IFS= read -r emoji; do
[ -z "$emoji" ] && continue
if LC_ALL=C printf '%s' "$CONTENT" | grep -qF -- "$emoji"; then
printf 'commit-msg: forbidden AI-signature emoji in commit message.\n' >&2
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
FAIL=1
break
fi
done <<<"$EMOJI_TELLS"
# Rule 5.1: AI-tell preamble phrasings at start of any line.
PREAMBLE_PATTERNS=(
"I'll now"
"Let me "
"Here is a quick summary"
"Here's a quick summary"
)
for p in "${PREAMBLE_PATTERNS[@]}"; do
if printf '%s' "$CONTENT" | grep -qE -- "^${p}"; then
printf 'commit-msg: forbidden AI-tell preamble at line start: %s\n' "$p" >&2
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
FAIL=1
fi
done
# Rule 5.1: Cursor attribution. Reject Cursor co-author trailers, the agent
# email, any @cursor.com address, and "generated with Cursor" style tells.
# Case-insensitive on purpose: cursoragent@cursor.com, Co-authored-by: Cursor.
CURSOR_LITERALS=(
'Co-Authored-By: Cursor'
'Co-authored-by: Cursor'
'Generated with Cursor'
'Generated by Cursor'
'with help from Cursor'
'Powered by Cursor'
)
for p in "${CURSOR_LITERALS[@]}"; do
if printf '%s' "$CONTENT" | grep -qiF -- "$p"; then
printf 'commit-msg: forbidden Cursor attribution: %s\n' "$p" >&2
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
FAIL=1
fi
done
if printf '%s' "$CONTENT" | grep -qiE -- '@cursor\.com'; then
printf 'commit-msg: forbidden Cursor email (@cursor.com) in commit message.\n' >&2
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
FAIL=1
fi
# Author / committer identity must not be Cursor. git var resolves the exact
# identity (including --author / GIT_*_IDENT overrides) this commit will record.
for ident in "$(git var GIT_AUTHOR_IDENT 2>/dev/null)" "$(git var GIT_COMMITTER_IDENT 2>/dev/null)"; do
[ -z "$ident" ] && continue
if printf '%s' "$ident" | grep -qiE -- '@cursor\.com|(^|[^[:alpha:]])cursor(agent)?([^[:alpha:]]|$)'; then
printf 'commit-msg: forbidden Cursor identity as author/committer: %s\n' "$ident" >&2
printf ' Fix: set git config user.name / user.email to your own identity.\n' >&2
FAIL=1
fi
done
exit $FAIL