#!/bin/sh # CI lint: flags Co-Authored-By trailers with Claude/Anthropic attribution in # PR commits. The hive convention in /knowledge/hive-rules.md forbids these # trailers — an attribution line in a prompt is not a control. This gate is. # # Emits a CI error annotation per offending commit and exits 1 if any commit # in the PR's range carries a prohibited trailer. Runs as its own CI job and # IS a required check on the forge (branch protection) — a hit blocks merge. # # Pattern: case-insensitive `co-authored-by` followed by `claude`, `anthropic`, # or `noreply@anthropic.com` anywhere in the line. Covers variants seen in the # wild: `Co-Authored-By: Claude`, `Co-Authored-By: Claude Sonnet 4.5 `. # # Scope: commits in the PR's range (BASE..HEAD), not the entire history — old # commits must not fail the build. In CI, BASE is the target branch # (GITHUB_BASE_REF / github.event.pull_request.base.ref in GH Actions; similar # vars in Forgejo). Falls back to origin/main for local runs. set -eu # Determine base ref: in CI (pull_request event), use the PR's base branch; # locally, default to origin/main. CI env vars: GITHUB_BASE_REF (GitHub # Actions), similar for Forgejo/Gitea (not standardized across runners, so we # check both common patterns). if [ -n "${GITHUB_BASE_REF:-}" ]; then # GitHub Actions / Forgejo Actions pull_request context base="origin/${GITHUB_BASE_REF}" elif [ -n "${CI_COMMIT_REF_NAME:-}" ]; then # GitLab-style variable (some Forgejo setups) base="origin/main" else # Local run or unknown CI — default to origin/main base="origin/main" fi # Ensure the base ref exists (in CI, actions/checkout@v3 fetches the PR head # but may not fetch the base branch by default — fetch it if missing). if ! git rev-parse --verify "$base" >/dev/null 2>&1; then # Extract branch name and fetch it branch="${base#origin/}" git fetch origin "$branch" 2>/dev/null || true # If still missing, fall back to main if ! git rev-parse --verify "$base" >/dev/null 2>&1; then base="origin/main" git fetch origin main 2>/dev/null || base="HEAD~10" fi fi # Get all commits in the PR's range. `--reverse` lists oldest first (clearer # for annotation output). `--format=%H` gets the full commit sha. commits="$(git log --reverse --format='%H' "${base}..HEAD" 2>/dev/null || true)" if [ -z "$commits" ]; then # No commits in range — either not on a PR branch, or nothing new. Pass. exit 0 fi # Check each commit's full message for the prohibited trailer pattern. # Case-insensitive grep for `co-authored-by` + any of `claude`, `anthropic`, # `noreply@anthropic.com`. hits="" for sha in $commits; do msg="$(git log -1 --format='%B' "$sha")" # Match case-insensitive co-authored-by AND (claude OR anthropic OR noreply@anthropic.com) # Use grep -E with -i for case-insensitive matching if printf '%s\n' "$msg" | grep -qiE 'co-authored-by.*claude|co-authored-by.*anthropic|co-authored-by.*noreply@anthropic\.com'; then # Found a hit — build annotation subject="$(git log -1 --format='%s' "$sha")" hits="${hits}${sha}|${subject} " fi done if [ -n "$hits" ]; then printf '%s' "$hits" | while IFS='|' read -r sha subject; do [ -z "$sha" ] && continue short_sha="$(printf '%s' "$sha" | cut -c1-8)" printf '::error title=Attribution trailer found in %s::%s — commit carries a Co-Authored-By trailer with Claude/Anthropic attribution, forbidden by /knowledge/hive-rules.md\n' \ "$short_sha" "$subject" done count="$(printf '%s' "$hits" | grep -c '|' || true)" printf 'check-attribution-trailers: %s commit(s) with prohibited trailers found\n' "$count" >&2 exit 1 fi exit 0