Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions commit_check/ai_signatures_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ def _body_marker(pattern: str, description: str = "") -> AiSignaturePattern:
r"|\d+\+Claude@users\.noreply\.github\.com)>)?",
"``Co-authored-by: Claude`` trailer",
),
# Any co-author name with the Anthropic noreply email — catches
# model-name variants such as "Claude Opus 4.5 (1M context)" that
# the pattern above misses.
_trailer(
"Co-authored-by",
r"[^<\n]*<noreply@anthropic\.com>",
"``Co-authored-by`` with Anthropic noreply email",
),
# Assisted-by trailer (Linux kernel style, with optional tool list)
_trailer(
"Assisted-by",
Expand Down Expand Up @@ -223,6 +231,16 @@ def _body_marker(pattern: str, description: str = "") -> AiSignaturePattern:
r"(?:claude|gpt|gemini)[\w.]*-[\w.-]+(?:\s*<[^>]*>)?",
"``Co-authored-by`` with AI model name",
),
# Catch space-separated AI model identifiers in Co-authored-by
# (e.g. "Claude Opus 4.5", "Gemini 2.5 Pro", "GPT 4 Turbo").
# A purely numeric version token is required so human names with
# ordinals ("Claude Dubois 3rd") are NOT flagged.
_trailer(
"Co-authored-by",
r"(?:claude|gpt|gemini)(?:\s+[a-z]+)*\s+\d+(?:\.\d+)*(?!\w)"
r"(?:\s+[a-z]+)*(?:\s*\([^)]*\))?(?:\s*<[^>]*>)?",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"``Co-authored-by`` with space-separated AI model name",
),
# Catch Assisted-by trailer (Linux kernel style) regardless of agent,
# with optional trailing tool list.
_trailer(
Expand Down
29 changes: 29 additions & 0 deletions tests/ai_signatures_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,35 @@ def test_human_claude_with_personal_email_ignored(self):
claude_hits = [s for s in result if s["tool"] == "Claude Code"]
assert len(claude_hits) == 0

@pytest.mark.benchmark
def test_claude_model_name_with_noreply_email(self):
"""Model-name co-author with anthropic noreply is detected."""
message = (
"feat: add feature\n\n"
"Co-authored-by: Claude Opus 4.5 (1M context) <noreply@anthropic.com>"
)
result = detect_ai_signatures(message)
assert any(s["tool"] == "Claude Code" for s in result)

@pytest.mark.benchmark
def test_space_separated_model_name_detected(self):
"""Space-separated model names with a version are detected."""
for trailer in (
"Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>",
"Co-authored-by: Gemini 2.5 Pro <gemini@example.com>",
"Co-authored-by: GPT 4 Turbo",
):
message = f"feat: add feature\n\n{trailer}"
assert has_ai_signature(message), trailer

@pytest.mark.benchmark
def test_human_name_with_ordinal_ignored(self):
"""A human name with an ordinal suffix is NOT detected."""
message = (
"feat: add feature\n\nCo-authored-by: Claude Dubois 3rd <claude@gmail.com>"
)
assert not has_ai_signature(message)

@pytest.mark.benchmark
def test_copilot_with_noreply_email(self):
"""Co-authored-by: Copilot with GitHub noreply is detected."""
Expand Down