ops: anchor attribution trailer lint to actual git trailer lines

The previous regex matched any line containing both 'co-authored-by'
and 'claude'/'anthropic' anywhere on the line, which flagged prose
*about* the trailer, not just the trailer itself. This caught the
commit that introduced the lint (subject: "ops: add CI lint for
Co-Authored-By/Claude/Anthropic trailers") as a false positive.

Anchor the match to `^[[:space:]]*co-authored-by:` so it only fires
on an actual trailer line (Key: value at line start), not a mention
in a commit subject or body. Add a --self-test mode with fixtures
covering three genuine-trailer spellings, an ordinary message, and
the PR's own subject line as the prose-mention regression case.

Refs #4432
This commit is contained in:
atlas 2026-09-15 23:16:20 +02:00 committed by mara
commit 9e618b3b90

View file

@ -2,14 +2,51 @@
# Flags Co-Authored-By trailers with Claude/Anthropic attribution in PR commits.
# The hive convention in /knowledge/hive-rules.md forbids these trailers.
#
# Pattern is loose (case-insensitive, matches `claude`, `anthropic`, or
# `noreply@anthropic.com` anywhere after `co-authored-by`) to cover variants
# like `Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>`.
# Pattern is anchored to an actual git trailer line — `^[[:space:]]*co-authored-by:`
# — not any mention of the phrase, then requires the value name `claude`,
# `anthropic`, or `noreply@anthropic.com`. Matching is case-insensitive and
# per-line (git trailers are one `Key: value` per line) to cover variants like
# `Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>` while letting a
# commit subject/body that merely *discusses* the trailer (e.g. this script's
# own "add CI lint for Co-Authored-By/Claude/Anthropic trailers") pass.
#
# Scope is the PR's commits (BASE..HEAD), not entire history — old commits
# must not fail the build.
#
# Run with --self-test to check the pattern against fixtures instead of
# scanning git history.
set -eu
trailer_re='^[[:space:]]*co-authored-by:.*(claude|anthropic|noreply@anthropic\.com)'
if [ "${1:-}" = "--self-test" ]; then
fail=0
assert_match() {
if ! printf '%s\n' "$2" | grep -qiE "$trailer_re"; then
echo "self-test FAILED (expected match): $1" >&2
fail=1
fi
}
assert_no_match() {
if printf '%s\n' "$2" | grep -qiE "$trailer_re"; then
echo "self-test FAILED (expected no match): $1" >&2
fail=1
fi
}
assert_match "bare trailer" 'Co-Authored-By: Claude'
assert_match "full trailer with email" 'Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>'
assert_match "lowercase trailer" 'co-authored-by: claude'
assert_no_match "ordinary message" 'fix: correct off-by-one in parser'
assert_no_match "prose mention of the trailer" 'ops: add CI lint for Co-Authored-By/Claude/Anthropic trailers'
if [ "$fail" -eq 0 ]; then
echo "self-test OK"
fi
exit "$fail"
fi
if [ -n "${GITHUB_BASE_REF:-}" ]; then
base="origin/${GITHUB_BASE_REF}"
else
@ -35,7 +72,7 @@ fi
hits=""
for sha in $commits; do
msg="$(git log -1 --format='%B' "$sha")"
if printf '%s\n' "$msg" | grep -qiE 'co-authored-by.*claude|co-authored-by.*anthropic|co-authored-by.*noreply@anthropic\.com'; then
if printf '%s\n' "$msg" | grep -qiE "$trailer_re"; then
subject="$(git log -1 --format='%s' "$sha")"
hits="${hits}${sha}|${subject}
"