The following patch originates in the SwiftTUI project.
From 9c63bbe5dfa7c69ba748d984b118d2b8170f2448 Mon Sep 17 00:00:00 2001
From: adamz <adam@zethrae.us>
Date: Mon, 8 Jun 2026 07:52:28 +0100
Subject: [PATCH] No AI co-authors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
AI co-authors are an (impressive) marketing hook — but provide
no useful commit signal.
It is June 2026. All code written is facilitated by a host of
sophisticated tools. AI agents are the most recent, but not always
the most notable non-human contributor.
AI 'co-authors' have high utility but no accountability. Tagging
them in commits waters down the _accountability_ chain — and at
worst the practice could imply similarly diffused _responsibility_.
SwiftTUI now forbids commits with these AI git co-authors to set
the expectations that:
* AI tools are probably involved in every commit.
* AI can not have responsibility or accountability for the code.
* The author is, as ever, the person — and as much as the act of
code authorship might change — the social rules for authorship
are not fundamentally changed by technology with which it is
performed.
Co-authored-by: The Swift Compiler <no-reply@swift.org>
---
AGENTS.md | 2 +
Scripts/check_commit_message_policy.sh | 116 +++++++++++++++++++++++++
docs/DEVELOPMENT.md | 3 +
prek.toml | 11 +++
4 files changed, 132 insertions(+)
create mode 100755 Scripts/check_commit_message_policy.sh
diff --git a/AGENTS.md b/AGENTS.md
index 3d9481ef..e33348ae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -124,6 +124,8 @@ Full policy in [docs/PUBLIC-API.md](docs/PUBLIC-API.md#anyview-policy).
`Synchronization` primitives.
- **main-thread-usage** — forbids bare `Thread.isMainThread` without
justification.
+- **no-ai-coauthors** — rejects commit messages with `Co-authored-by:` trailers
+ whose entry matches common AI model names or AI-bot email identities.
## Tests
diff --git a/Scripts/check_commit_message_policy.sh b/Scripts/check_commit_message_policy.sh
new file mode 100755
index 00000000..3b678d37
--- /dev/null
+++ b/Scripts/check_commit_message_policy.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env sh
+
+set -eu
+
+if [ "$#" -ne 1 ]; then
+ >&2 echo "usage: $0 <commit-message-file>"
+ exit 2
+fi
+
+commit_message_file=$1
+
+if [ ! -f "$commit_message_file" ]; then
+ >&2 echo "Commit message file does not exist: $commit_message_file"
+ exit 2
+fi
+
+violations=$(
+ awk '
+ function trim(s) {
+ sub(/^[[:space:]]+/, "", s)
+ sub(/[[:space:]]+$/, "", s)
+ return s
+ }
+
+ function normalize_identity(s) {
+ s = tolower(s)
+ gsub(/[^a-z0-9]+/, " ", s)
+ return trim(s)
+ }
+
+ function blocked_display_name(name, raw) {
+ raw = normalize_identity(name)
+ name = raw
+
+ if (raw == "replit agent") return 1
+ sub(/ (ai|assistant|bot|chatbot)$/, "", name)
+
+ if (name ~ /^(openai )?(chatgpt|chat gpt)( [0-9]+[a-z]?)*$/) return 1
+ if (name ~ /^(openai )?gpt( [0-9]+[a-z]?)*$/) return 1
+ if (name ~ /^(openai )?codex( (cli|code))?$/) return 1
+ if (name ~ /^(openai|anthropic)$/) return 1
+ if (name ~ /^(anthropic )?claude( (sonnet|opus|haiku|code|[0-9]+[a-z]?))*$/) return 1
+ if (name ~ /^(google )?(gemini|bard)( ([0-9]+[a-z]?|flash|pro|ultra))*$/) return 1
+ if (name ~ /^(github )?copilot( (chat|code))?$/) return 1
+ if (name ~ /^perplexity$/) return 1
+ if (name ~ /^(meta )?llama( [0-9]+[a-z]?)?$/) return 1
+ if (name ~ /^(mistral|mixtral)( [0-9]+[a-z]?)?$/) return 1
+ if (name ~ /^deepseek( [a-z0-9]+)*$/) return 1
+ if (name ~ /^qwen( [0-9]+[a-z]?)*$/) return 1
+ if (name ~ /^(xai )?grok( [0-9]+[a-z]?)?$/) return 1
+ if (name ~ /^(meta ai|ollama|cursor|windsurf|devin|amazon q|amazon q developer|q developer)$/) return 1
+ if (name ~ /^(tabnine|cody|sourcegraph cody)$/) return 1
+
+ return 0
+ }
+
+ function extract_email(entry, candidate) {
+ candidate = entry
+ if (match(candidate, /<[^>]+>/)) {
+ candidate = substr(candidate, RSTART + 1, RLENGTH - 2)
+ } else if (match(candidate, /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z0-9.-]+/)) {
+ candidate = substr(candidate, RSTART, RLENGTH)
+ } else {
+ candidate = ""
+ }
+ return tolower(trim(candidate))
+ }
+
+ function blocked_email(email, local, compact) {
+ if (email == "") return 0
+
+ local = email
+ sub(/@.*/, "", local)
+ compact = local
+ gsub(/[^a-z0-9]+/, "", compact)
+
+ if (compact ~ /^(chatgpt|gpt([0-9]+[a-z]?)?|openai|codex)$/) return 1
+ if (compact ~ /^(claude(sonnet|opus|haiku|code|[0-9]+[a-z]?)?|anthropic)$/) return 1
+ if (compact ~ /^(gemini|bard|copilot|githubcopilot)$/) return 1
+ if (compact ~ /^(perplexity|llama([0-9]+[a-z]?)?|mistral|mixtral)$/) return 1
+ if (compact ~ /^(deepseek(r[0-9]+|v[0-9]+)?|qwen([0-9]+[a-z]?)?|grok|xai)$/) return 1
+ if (compact ~ /^(ollama|cursor|windsurf|devin|replitagent)$/) return 1
+ if (compact ~ /^(amazonq|qdeveloper|tabnine|cody|sourcegraphcody)$/) return 1
+
+ return 0
+ }
+
+ /^[[:space:]]*#/ { next }
+
+ {
+ lower = tolower($0)
+ if (lower !~ /^[[:space:]]*co-authored-by[[:space:]]*:/) next
+
+ entry = lower
+ sub(/^[[:space:]]*co-authored-by[[:space:]]*:[[:space:]]*/, "", entry)
+
+ display_name = entry
+ sub(/[[:space:]]*<.*/, "", display_name)
+
+ email = extract_email(entry)
+
+ if (blocked_display_name(display_name) || blocked_email(email)) {
+ printf "%d: %s\n", NR, $0
+ }
+ }
+ ' "$commit_message_file"
+)
+
+if [ -n "$violations" ]; then
+ >&2 echo "AI model identities must not be added as Co-authored-by trailers."
+ >&2 echo "Remove the AI Co-authored-by entry from the commit message."
+ >&2 echo ""
+ >&2 echo "Offending trailer lines:"
+ >&2 printf '%s\n' "$violations"
+ exit 1
+fi
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index 64fa46cd..1d594c17 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -92,6 +92,9 @@ Hooks run through `prek` (`prek.toml`):
`SwiftTUICore`, `SwiftTUIViews`, and `SwiftTUI`.
- `public-surface-policies`, `structured-concurrency-escape-hatches`,
`main-thread-usage` — the source-policy checks.
+- `no-ai-coauthors` — the commit-message hook rejects `Co-authored-by:`
+ trailers whose entry matches common AI model names or AI-bot email
+ identities.
## Rendered text fixtures
diff --git a/prek.toml b/prek.toml
index 1246b102..75be390a 100644
--- a/prek.toml
+++ b/prek.toml
@@ -2,9 +2,20 @@
# See https://prek.j178.dev for more information.
#:schema https://www.schemastore.org/prek.json
+default_install_hook_types = ["pre-commit", "commit-msg"]
+
[[repos]]
repo = "local"
hooks = [
+ {
+ id = "no-ai-coauthors",
+ name = "forbid AI model Co-authored-by trailers",
+ entry = "./Scripts/check_commit_message_policy.sh",
+ language = "system",
+ stages = ["commit-msg"],
+ always_run = true,
+ pass_filenames = true
+ },
{
id = "swift-format",
name = "run swift-format format",