chore(hooks): block Cursor attribution locally and in CI
Reject Cursor co-author trailers, cursor.com agent emails, and machine attribution tells in commit messages and staged content, plus a Cursor author or committer identity. Add a workflow enforcing the same rule server-side on push and pull request.
This commit is contained in:
parent
7006d1eb52
commit
28cfd3839d
@ -88,4 +88,39 @@ for p in "${PREAMBLE_PATTERNS[@]}"; do
|
||||
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
|
||||
|
||||
@ -73,8 +73,12 @@ SKIP_PATTERNS=(
|
||||
# .githooks/pre-commit) rather than the canonical *-style-check names.
|
||||
'/commit-msg$'
|
||||
'/pre-commit$'
|
||||
'/pre-push$'
|
||||
'/check-style\.sh$'
|
||||
'/check-staged-style\.sh$'
|
||||
# The CI workflow that enforces the same Cursor prohibition server-side; it
|
||||
# must quote the forbidden tokens to scan for them.
|
||||
'^\.github/workflows/no-cursor-attribution\.yml$'
|
||||
)
|
||||
|
||||
# Filter STAGED_FILES through the skip list.
|
||||
@ -147,6 +151,16 @@ LITERAL_PATTERNS=(
|
||||
'This change was generated'
|
||||
)
|
||||
|
||||
# Cursor-attribution literal tells (case-insensitive at the call site).
|
||||
CURSOR_LITERALS=(
|
||||
'Co-Authored-By: Cursor'
|
||||
'Co-authored-by: Cursor'
|
||||
'Generated with Cursor'
|
||||
'Generated by Cursor'
|
||||
'with help from Cursor'
|
||||
'Powered by Cursor'
|
||||
)
|
||||
|
||||
# AI-tell signature emojis (UTF-8 byte sequences kept in a variable so the
|
||||
# script source stays clean of the glyphs themselves).
|
||||
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')
|
||||
@ -180,6 +194,21 @@ while IFS= read -r file; do
|
||||
fi
|
||||
done
|
||||
|
||||
# Cursor-attribution check (mirrors commit-msg). Specific tells only, never
|
||||
# the bare word "cursor" (a legitimate programming term: text/db cursor).
|
||||
for p in "${CURSOR_LITERALS[@]}"; do
|
||||
if printf '%s' "$ADDED" | grep -qiF -- "$p"; then
|
||||
printf 'pre-commit: forbidden Cursor attribution in staged %s: %s\n' "$file" "$p" >&2
|
||||
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
|
||||
FAIL=1
|
||||
fi
|
||||
done
|
||||
if printf '%s' "$ADDED" | grep -qiE -- '@cursor\.com'; then
|
||||
printf 'pre-commit: forbidden Cursor email (@cursor.com) in staged %s.\n' "$file" >&2
|
||||
printf ' Rule: github-discipline.md Rule 5.1\n' >&2
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# AI-signature emoji check.
|
||||
while IFS= read -r emoji; do
|
||||
[ -z "$emoji" ] && continue
|
||||
|
||||
60
.github/workflows/no-cursor-attribution.yml
vendored
Normal file
60
.github/workflows/no-cursor-attribution.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: No Cursor attribution
|
||||
|
||||
# Server-side companion to the local .githooks/commit-msg and pre-commit guards.
|
||||
# Fails the check if any commit pushed (or in a PR) carries Cursor attribution:
|
||||
# a Cursor co-author trailer, an @cursor.com email, or a "generated with Cursor"
|
||||
# tell, in the commit message or in the author/committer identity.
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan commits
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Reject Cursor attribution
|
||||
env:
|
||||
EVENT: ${{ github.event_name }}
|
||||
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve the set of commits to scan.
|
||||
if [ "$EVENT" = "pull_request" ]; then
|
||||
range="${PR_BASE}..${PR_HEAD}"
|
||||
elif [ -n "${PUSH_BEFORE:-}" ] && [ "$PUSH_BEFORE" != "0000000000000000000000000000000000000000" ]; then
|
||||
range="${PUSH_BEFORE}..${SHA}"
|
||||
else
|
||||
# New branch / tag push or manual run: scan just the tip commit.
|
||||
range="${SHA}~1..${SHA}"
|
||||
git rev-parse "${SHA}~1" >/dev/null 2>&1 || range="$SHA"
|
||||
fi
|
||||
echo "Scanning commit range: $range"
|
||||
|
||||
# Emit message + author + committer for each commit, then grep.
|
||||
fmt='commit %H%nauthor: %an <%ae>%ncommitter: %cn <%ce>%n%B'
|
||||
if printf '%s' "$range" | grep -q '\.\.'; then
|
||||
log=$(git log --format="$fmt" "$range")
|
||||
else
|
||||
log=$(git log -1 --format="$fmt" "$range")
|
||||
fi
|
||||
|
||||
pattern='cursoragent@cursor\.com|@cursor\.com|Co-authored-by:[[:space:]]*Cursor|Generated (with|by) Cursor|with help from Cursor|Powered by Cursor'
|
||||
if printf '%s\n' "$log" | grep -niE -- "$pattern"; then
|
||||
echo "::error::Cursor attribution found in the commits above. Strip it (rewrite the commits) before pushing."
|
||||
exit 1
|
||||
fi
|
||||
echo "No Cursor attribution found."
|
||||
Loading…
Reference in New Issue
Block a user