Skip to content

feat: add AI attribution governance (forbid known AI tool signatures) - #456

Merged
shenxianpeng merged 10 commits into
mainfrom
feature/ai-attribution-governance
Jul 6, 2026
Merged

feat: add AI attribution governance (forbid known AI tool signatures)#456
shenxianpeng merged 10 commits into
mainfrom
feature/ai-attribution-governance

Conversation

@shenxianpeng

@shenxianpeng shenxianpeng commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

Add AI attribution governance to commit-check, enabling projects to forbid commits that contain known AI tool signatures at the commit-message level. This directly addresses the ongoing industry-wide discussion around AI disclosure in open source.

Background

The open-source ecosystem is converging on the need for AI attribution policies, but no tool exists at the CI level to help projects enforce them. This PR fills that gap:

commit-check is uniquely positioned to be the neutral enforcement layer.

Configuration

[commit]
# "ignore" (default) | "forbid"
ai_attribution = "forbid"

A single binary switch:

  • "ignore" -- no validation (default, backward compatible)
  • "forbid" -- rejects any commit with known AI tool signatures, regardless of trailer style (Co-authored-by, Assisted-by, body markers, etc.)

No require mode, no ai_trailer_style -- the signature database recognizes all known formats automatically.

Key Components

commit_check/ai_signatures.py -- Curated AI tool signature database

Detects known AI tool markers in commit messages, including:

Tool Patterns matched
Claude Code Co-authored-by: Claude, Assisted-by: Claude:<model> [tools], 🤖 Generated with Claude, Claude-Session:, Claude-Workflow:
GitHub Copilot Co-authored-by: Copilot
OpenAI Codex Co-authored-by: Codex
Gemini Co-authored-by: Gemini
Cursor Co-authored-by: Cursor
Devin Co-authored-by: Devin
Aider Co-authored-by: Aider, Co-authored-by: ... (aider)
Windsurf Co-authored-by: Windsurf
Tabby Co-authored-by: Tabby
Generic AI Assisted-by: <tool>:<model> [tools] (kernel style), model names (claude-sonnet-4, gpt-4-turbo)

Built-in false positive prevention

  • Claude/Devin/Copilot patterns anchor to known noreply email addresses to avoid matching human co-authors named Claude or Devin
  • Human co-authors like Co-authored-by: Jane Doe <jane@example.com> are never flagged
  • Kernel-style Assisted-by: format accepts optional trailing tool list (Claude:claude-3-opus coccinelle sparse)

Full integration

  • CLI: --ai-attribution=forbid
  • TOML config: [commit] ai_attribution = "forbid"
  • Environment variables: CCHK_AI_ATTRIBUTION=forbid
  • Python API: validate_message() includes AI attribution checks
  • --format json: AI check results included in structured output

Test coverage

414 tests pass with zero regressions:

  • All AI tool signature detection patterns validated (including kernel format with tool list, generic model names, emoji markers, aider suffix)
  • Human-name false-positive regression tests
  • Text output checks (no template leak)
  • forbid/ignore policy coverage
  • Empty message and edge cases
  • Configuration/rule builder integration
  • VALIDATOR_MAP registration

Documentation updated

  • README.rst -- overview mentions AI attribution, example config includes ai_attribution
  • docs/configuration.rst -- option table, env var mapping, CLI mapping, and example config
  • docs/what-is-new.rst -- full v2.10.0 changelog entry with detected tools list

Future work (not in this PR)

  1. Action/App: PR summary showing AI disclosure status per commit
  2. MCP server: AI agents query describe_validation_rules to auto-comply
  3. --format json: Richer AI signature metadata for SBOM/audit tooling

Summary by CodeRabbit

  • New Features

    • Added an AI attribution setting for commit validation, with options to ignore or forbid known AI-generated signatures.
    • Added a new command-line option and configuration support for controlling this behavior.
    • Expanded detection to recognize more AI signature formats in commit messages.
  • Bug Fixes

    • Commit validation now reports AI attribution policy violations when forbidden signatures are found.
  • Documentation

    • Updated configuration and release documentation with examples, supported values, and behavior details.

Introduce a new validation subsystem that lets projects enforce their
AI contribution policy at the commit-message level.  This feature is
motivated by the ongoing industry-wide discussion around AI disclosure
in open source (CPython, Linux kernel, VS Code, Apache, Fedora, etc.)
and requires no external dependencies.

Configuration (under [commit]):
  ai_attribution = "forbid" | "require" | "ignore" (default: ignore)
  ai_trailer_style = "assisted-by" | "co-authored-by" (default: assisted-by)

Key components:
- commit_check/ai_signatures.py — curated database of known AI tool
  signatures (Claude Code, Copilot, Codex, Gemini, Cursor, Devin,
  Aider, Windsurf, Tabby, and generic AI patterns)
- AiAttributionValidator — three-mode policy engine
- AiTrailerStyleValidator — enforces Linux kernel Assisted-by or
  GitHub Co-authored-by style
- Full CLI, API, env-var, and TOML config integration
- 52 new tests covering all modes and detection patterns
@shenxianpeng
shenxianpeng requested a review from a team as a code owner July 5, 2026 20:16
@netlify

netlify Bot commented Jul 5, 2026

Copy link
Copy Markdown

Deploy Preview for commit-check ready!

Name Link
🔨 Latest commit 48d9a17
🔍 Latest deploy log https://app.netlify.com/projects/commit-check/deploys/6a4b67801dba3e0008406447
😎 Deploy Preview https://deploy-preview-456--commit-check.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added enhancement New feature or request tests Add test related changes labels Jul 5, 2026
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds AI attribution enforcement for commit messages. Introduces AI signature data and detection helpers, wires a new ai_attribution policy through configuration, rule building, validation, CLI, and API paths, and adds tests plus documentation for the new behavior.

Changes

AI Attribution Feature

Layer / File(s) Summary
Signature data registry
commit_check/ai_signatures_data.py
Defines AI signature dataclasses, helper regex builders, curated tool signatures, and the ordered registry of known tools.
Signature detection API
commit_check/ai_signatures.py
Builds the public pattern list from the registry and implements detect_ai_signatures() and has_ai_signature().
Config defaults and rule definition
commit_check/__init__.py, commit_check/config_merger.py, commit_check/rules_catalog.py, commit_check/rule_builder.py
Adds the default AI attribution policy, merges it into config sources, registers the ai_attribution rule, and builds the rule from commit config.
Validator and CLI wiring
commit_check/engine.py, commit_check/main.py, commit_check/api.py
Adds AiAttributionValidator, registers it with the engine, and exposes the policy through CLI and message validation request wiring.
Tests and documentation
tests/ai_signatures_test.py, tests/engine_test.py, tests/rule_builder_test.py, README.rst, docs/configuration.rst, docs/what-is-new.rst
Adds coverage for detection, rule construction, engine validation, and documents the new ai_attribution configuration and release note entry.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Suggested labels: major

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding AI attribution governance to forbid known AI tool signatures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ai-attribution-governance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.10%. Comparing base (7a4a7cf) to head (48d9a17).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #456      +/-   ##
==========================================
+ Coverage   96.89%   97.10%   +0.21%     
==========================================
  Files          10       12       +2     
  Lines        1094     1176      +82     
==========================================
+ Hits         1060     1142      +82     
  Misses         34       34              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (8)
tests/ai_signatures_test.py (1)

206-210: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused loop variable regex.

Ruff (B007) flags regex as unused within the loop body.

♻️ Proposed fix
-        for regex, tool_name, desc in ALL_PATTERNS:
+        for _regex, tool_name, desc in ALL_PATTERNS:
             assert desc, f"Pattern for {tool_name} is missing a description"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ai_signatures_test.py` around lines 206 - 210, The loop in
test_all_patterns_have_description uses ALL_PATTERNS but never reads the regex
value, so Ruff flags the unused variable. Update the iteration in
test_all_patterns_have_description to ignore that tuple element (for example by
using a placeholder name) while keeping tool_name and desc checks unchanged, so
the intent stays clear and the B007 warning is removed.

Source: Linters/SAST tools

commit_check/rule_builder.py (1)

283-307: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Confirm ai_trailer_style rule should be built under forbid too.

This rule is built whenever ai_attribution != "ignore", including "forbid". But per the AiTrailerStyleValidator docstring in engine.py, it's meant as "a companion to ai_attribution = 'require'". Under forbid, any detected AI signature is already rejected by AiAttributionValidator; additionally running the trailer-style check on the same message could produce a second, redundant failure for what is effectively a single underlying violation.

If this is intentional (e.g., to also surface style feedback under forbid), consider ignoring; otherwise restrict rule construction to policy == "require".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/rule_builder.py` around lines 283 - 307, The
_build_ai_trailer_style_rule method currently creates the ai_trailer_style
ValidationRule for any ai_attribution value except ignore, which includes forbid
and can cause redundant failures. Update the rule construction logic in
_build_ai_trailer_style_rule so it only builds the trailer-style rule when
ai_attribution is require, matching the AiTrailerStyleValidator intent in
engine.py; keep the existing ai_trailer_style validation and ValidationRule
wiring unchanged otherwise.
commit_check/rules_catalog.py (1)

107-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused {reason}/{suggestion} templates.

Per the AiAttributionValidator/AiTrailerStyleValidator implementations in engine.py, failures are recorded with fully-formed error/suggest strings passed directly to _record_failure(...), not via catalog_entry.error.format(reason=...) or catalog_entry.suggest.format(suggestion=...). These templated placeholders appear to be dead code that could mislead readers into thinking they're filled in.

Consider either wiring these templates into the validators' _record_failure calls, or simplifying the catalog entries to static text if the templating is unused.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/rules_catalog.py` around lines 107 - 118, The ai_attribution and
ai_trailer_style catalog entries are using dead `{reason}` and `{suggestion}`
templates that the validators do not populate. Update the
`AiAttributionValidator` and `AiTrailerStyleValidator` paths in engine.py to
either pass formatted values into `_record_failure(...)` and consume the
templates, or simplify the corresponding `RuleCatalogEntry` `error`/`suggest`
fields in `rules_catalog.py` to static text. Keep the catalog and validator
behavior aligned so the `check` names and failure messages are consistent.
tests/rule_builder_test.py (1)

375-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for an invalid ai_attribution value.

Given the validation gap flagged in rule_builder.py (_build_ai_attribution_rule only special-cases "ignore"), a test asserting behavior for an unrecognized value (e.g. "typo") would help pin down the intended contract once that's addressed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/rule_builder_test.py` around lines 375 - 491, Add a test in
TestAiAttributionRuleBuilder for an invalid ai_attribution value such as "typo"
to define the expected contract for RuleBuilder._build_ai_attribution_rule. Set
up a commit config with the bad value, build the rule via
_build_ai_attribution_rule, and assert the intended outcome explicitly (for
example, reject/return None or raise) so the behavior is pinned down alongside
the existing ignore/forbid/require cases.
commit_check/engine.py (2)

785-802: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate _record_failure implementation across both validators.

AiAttributionValidator._record_failure and AiTrailerStyleValidator._record_failure are byte-for-byte identical. Consider hoisting this into BaseValidator (which already has a similarly-shaped _print_failure) to avoid drift between the two copies.

♻️ Proposed consolidation
 class BaseValidator(ABC):
     ...
+    def _record_failure(self, value: str, error: str, suggest: str) -> None:
+        """Record a failure with dynamic error/suggest messages."""
+        self._last_failure = {
+            "check": self.rule.check,
+            "value": value,
+            "error": error,
+            "suggest": suggest,
+        }
+        if not self._suppress_output:
+            rule_dict = self.rule.to_dict()
+            from commit_check.util import _print_failure
+
+            _print_failure(
+                rule_dict,
+                value,
+                no_banner=self._no_banner,
+                compact=self._compact,
+            )

Then remove the duplicated method bodies from both AiAttributionValidator and AiTrailerStyleValidator.

Also applies to: 855-872

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/engine.py` around lines 785 - 802, The `_record_failure` logic
is duplicated in both `AiAttributionValidator` and `AiTrailerStyleValidator`, so
consolidate it into `BaseValidator` alongside `_print_failure`-style handling.
Move the shared implementation into `BaseValidator._record_failure` using the
existing `self.rule`, `self._last_failure`, `self._suppress_output`, and
`_print_failure` flow, then remove the copied method bodies from both validator
classes so they inherit the shared behavior.

728-783: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce cognitive complexity of AiAttributionValidator.validate().

SonarCloud flags this method's cognitive complexity at 21 vs. the allowed 15. Consider extracting the forbid and require branches into private helper methods (e.g. _validate_forbid, _validate_require) to improve readability and satisfy the linter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/engine.py` around lines 728 - 783, The validate method in
AiAttributionValidator is too complex and should be simplified by extracting the
policy-specific logic into private helpers. Move the "forbid" and "require"
branches out of validate() into dedicated methods such as _validate_forbid and
_validate_require, and have validate() only handle the early exits, policy
dispatch, and result return. Keep the existing behavior and reuse the current
helpers like detect_ai_signatures, find_co_authored_by_ai, and
find_assisted_by_trailers inside the new methods.

Source: Linters/SAST tools

tests/engine_test.py (2)

1630-1630: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated string literals flagged by static analysis.

SonarCloud flags the literal "feat: add feature\n\nCo-authored-by: Claude <noreply@anthropic.com>" (and the Assisted-by equivalent) as duplicated several times across tests. Consider extracting shared fixture strings to module-level constants.

Also applies to: 1643-1643, 1699-1699, 1741-1741, 1754-1754, 1798-1798, 1837-1837

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/engine_test.py` at line 1630, The test strings used in the
commit-message assertions are duplicated across multiple cases, so extract the
shared `"feat: add feature\n\nCo-authored-by: Claude <noreply@anthropic.com>"`
and Assisted-by variants into module-level constants in the test module. Update
the affected test functions that reference these literals to use the shared
constants instead, keeping the existing assertions and commit-message behavior
unchanged.

Source: Linters/SAST tools


1619-1773: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing test for signature-without-trailer under require policy.

None of the TestAiAttributionValidator tests cover a message with a detected AI signature via a non-trailer body_marker pattern and no Co-authored-by/Assisted-by trailer at all under require policy. This is exactly the gap flagged in engine.py's AiAttributionValidator.validate() (require branch) — currently that scenario incorrectly PASSes. Adding a test would have caught it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/engine_test.py` around lines 1619 - 1773, Add a missing
AiAttributionValidator test for the require policy covering a commit that
contains an AI signature detected via a body_marker pattern but no
Co-authored-by or Assisted-by trailer. Use TestAiAttributionValidator and
AiAttributionValidator.validate with ValidationRule(check="ai_attribution",
value="require", allowed=[...]) to assert this message fails, since the current
require branch in validate() incorrectly passes that case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@commit_check/ai_signatures.py`:
- Around line 227-232: The generic Co-authored-by AI trailer pattern in
ai_signatures.py is too strict and only matches bare model names, so it misses
documented suffix forms like claude-sonnet or gpt-4. Update the _trailer pattern
passed for Co-authored-by so it matches model-name prefixes plus optional suffix
characters before the email/address part, while still allowing the trailer to
terminate correctly; keep the change localized to the Co-authored-by signature
entry and preserve the existing AI model family coverage.
- Around line 262-306: The signature kind is being reconstructed incorrectly
from the regex prefix, which mislabels some body markers as trailers. Update the
flattened registry in ALL_PATTERNS to preserve AiSignaturePattern.kind alongside
the regex, tool name, and description, then use that stored kind in
detect_ai_signatures instead of checking regex.pattern.startswith("^"). Also
adjust the tuple unpacking in has_ai_signature, find_co_authored_by_ai, and
find_assisted_by_trailers to match the new ALL_PATTERNS shape.

In `@commit_check/engine.py`:
- Around line 753-781: The require path in the commit validation logic only
checks for mismatched trailer styles and can incorrectly PASS when AI signatures
are present but no valid trailer exists. Update the require branch in
`commit_check.engine`’s validation flow to explicitly fail whenever
`detect_ai_signatures` returns matches but neither `find_co_authored_by_ai` nor
`find_assisted_by_trailers` finds the configured trailer style, and keep the
existing style-mismatch failures for the wrong trailer type. Add a clear failure
via `_record_failure` using the same `ValidationResult.FAIL` path so `require`
truly enforces that a correctly styled AI attribution trailer is present.
- Around line 716-874: Avoid running both AI validators on the same commit:
AiAttributionValidator already checks trailer style when ai_attribution is
"require", so AiTrailerStyleValidator should not also fire for that policy.
Update the validation flow in AiAttributionValidator and AiTrailerStyleValidator
so the style-specific check is only active when needed, and adjust the request
path in api.py/main.py to avoid requesting ai_trailer_style for redundant cases
or centralize the style enforcement in one validator to prevent duplicate FAILs
and double scanning.

In `@commit_check/rule_builder.py`:
- Around line 257-281: Validate the ai_attribution policy in
_build_ai_attribution_rule before creating the ValidationRule: right now only
"ignore" is handled and typos can slip through silently. In
RuleBuilder._build_ai_attribution_rule, check the resolved value from
self.commit_config.get("ai_attribution", DEFAULT_AI_ATTRIBUTION) against
{"ignore", "forbid", "require"} and fail loudly or raise an error for anything
else, so invalid config/CLI input does not produce a no-op validator. Keep the
existing handling for the valid modes and the trailer style lookup unchanged.

In `@tests/ai_signatures_test.py`:
- Around line 115-119: The assertion in test_all_patterns_compile is
tautological and never validates the regex result. Update the check in
test_all_patterns_compile so it actually exercises regex.search or another
compilation/validation path for each entry in ALL_PATTERNS, and remove the
unconditional or True. Keep the existing tool_name and desc context in the
failure message so a bad pattern is still easy to identify.

---

Nitpick comments:
In `@commit_check/engine.py`:
- Around line 785-802: The `_record_failure` logic is duplicated in both
`AiAttributionValidator` and `AiTrailerStyleValidator`, so consolidate it into
`BaseValidator` alongside `_print_failure`-style handling. Move the shared
implementation into `BaseValidator._record_failure` using the existing
`self.rule`, `self._last_failure`, `self._suppress_output`, and `_print_failure`
flow, then remove the copied method bodies from both validator classes so they
inherit the shared behavior.
- Around line 728-783: The validate method in AiAttributionValidator is too
complex and should be simplified by extracting the policy-specific logic into
private helpers. Move the "forbid" and "require" branches out of validate() into
dedicated methods such as _validate_forbid and _validate_require, and have
validate() only handle the early exits, policy dispatch, and result return. Keep
the existing behavior and reuse the current helpers like detect_ai_signatures,
find_co_authored_by_ai, and find_assisted_by_trailers inside the new methods.

In `@commit_check/rule_builder.py`:
- Around line 283-307: The _build_ai_trailer_style_rule method currently creates
the ai_trailer_style ValidationRule for any ai_attribution value except ignore,
which includes forbid and can cause redundant failures. Update the rule
construction logic in _build_ai_trailer_style_rule so it only builds the
trailer-style rule when ai_attribution is require, matching the
AiTrailerStyleValidator intent in engine.py; keep the existing ai_trailer_style
validation and ValidationRule wiring unchanged otherwise.

In `@commit_check/rules_catalog.py`:
- Around line 107-118: The ai_attribution and ai_trailer_style catalog entries
are using dead `{reason}` and `{suggestion}` templates that the validators do
not populate. Update the `AiAttributionValidator` and `AiTrailerStyleValidator`
paths in engine.py to either pass formatted values into `_record_failure(...)`
and consume the templates, or simplify the corresponding `RuleCatalogEntry`
`error`/`suggest` fields in `rules_catalog.py` to static text. Keep the catalog
and validator behavior aligned so the `check` names and failure messages are
consistent.

In `@tests/ai_signatures_test.py`:
- Around line 206-210: The loop in test_all_patterns_have_description uses
ALL_PATTERNS but never reads the regex value, so Ruff flags the unused variable.
Update the iteration in test_all_patterns_have_description to ignore that tuple
element (for example by using a placeholder name) while keeping tool_name and
desc checks unchanged, so the intent stays clear and the B007 warning is
removed.

In `@tests/engine_test.py`:
- Line 1630: The test strings used in the commit-message assertions are
duplicated across multiple cases, so extract the shared `"feat: add
feature\n\nCo-authored-by: Claude <noreply@anthropic.com>"` and Assisted-by
variants into module-level constants in the test module. Update the affected
test functions that reference these literals to use the shared constants
instead, keeping the existing assertions and commit-message behavior unchanged.
- Around line 1619-1773: Add a missing AiAttributionValidator test for the
require policy covering a commit that contains an AI signature detected via a
body_marker pattern but no Co-authored-by or Assisted-by trailer. Use
TestAiAttributionValidator and AiAttributionValidator.validate with
ValidationRule(check="ai_attribution", value="require", allowed=[...]) to assert
this message fails, since the current require branch in validate() incorrectly
passes that case.

In `@tests/rule_builder_test.py`:
- Around line 375-491: Add a test in TestAiAttributionRuleBuilder for an invalid
ai_attribution value such as "typo" to define the expected contract for
RuleBuilder._build_ai_attribution_rule. Set up a commit config with the bad
value, build the rule via _build_ai_attribution_rule, and assert the intended
outcome explicitly (for example, reject/return None or raise) so the behavior is
pinned down alongside the existing ignore/forbid/require cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d8da658-2676-4758-af02-62600c0a929c

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4a7cf and 0c9a4e2.

📒 Files selected for processing (11)
  • commit_check/__init__.py
  • commit_check/ai_signatures.py
  • commit_check/api.py
  • commit_check/config_merger.py
  • commit_check/engine.py
  • commit_check/main.py
  • commit_check/rule_builder.py
  • commit_check/rules_catalog.py
  • tests/ai_signatures_test.py
  • tests/engine_test.py
  • tests/rule_builder_test.py

Comment thread commit_check/ai_signatures.py Outdated
Comment thread commit_check/ai_signatures.py Outdated
Comment thread commit_check/engine.py
Comment thread commit_check/engine.py Outdated
Comment on lines +257 to +281
def _build_ai_attribution_rule(
self, catalog_entry: RuleCatalogEntry
) -> ValidationRule | None:
"""Build AI attribution validation rule.

Three modes:
* ``"forbid"`` — reject any commit with AI tool signatures
* ``"require"`` — if AI signatures present, must use preferred style
* ``"ignore"`` — no validation (default, returns None)
"""
policy = self.commit_config.get("ai_attribution", DEFAULT_AI_ATTRIBUTION)
if policy == "ignore":
return None

trailer_style = self.commit_config.get(
"ai_trailer_style", DEFAULT_AI_TRAILER_STYLE
)

return ValidationRule(
check=catalog_entry.check,
value=policy,
error=catalog_entry.error or "",
suggest=catalog_entry.suggest or "",
allowed=[trailer_style],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject invalid ai_attribution values before building the rule

ai_attribution only special-cases "ignore" here; a typo from config/CLI is passed through and can make the validator silently do nothing. Validate the policy against {"ignore", "forbid", "require"} or fail loudly before constructing the rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/rule_builder.py` around lines 257 - 281, Validate the
ai_attribution policy in _build_ai_attribution_rule before creating the
ValidationRule: right now only "ignore" is handled and typos can slip through
silently. In RuleBuilder._build_ai_attribution_rule, check the resolved value
from self.commit_config.get("ai_attribution", DEFAULT_AI_ATTRIBUTION) against
{"ignore", "forbid", "require"} and fail loudly or raise an error for anything
else, so invalid config/CLI input does not produce a no-op validator. Keep the
existing handling for the valid modes and the trailer style lookup unchanged.

Comment thread tests/ai_signatures_test.py Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 5, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 312 untouched benchmarks
🆕 46 new benchmarks
⏩ 109 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 test_all_known_tools_have_patterns N/A 102.3 µs N/A
🆕 test_all_patterns_compile N/A 106.3 µs N/A
🆕 test_claude_session_trailer N/A 170.4 µs N/A
🆕 test_dedup_matched_text N/A 174.4 µs N/A
🆕 test_emoji_marker_detected N/A 172 µs N/A
🆕 test_human_co_author_not_detected N/A 171.1 µs N/A
🆕 test_multiple_ai_tools_detected N/A 228.3 µs N/A
🆕 test_no_signatures_in_clean_commit N/A 177.8 µs N/A
🆕 test_clean_message N/A 113.6 µs N/A
🆕 test_empty_message N/A 111 µs N/A
🆕 test_with_ai_signature N/A 108.1 µs N/A
🆕 test_all_patterns_have_description N/A 101.3 µs N/A
🆕 test_all_tools_have_unique_names N/A 105.2 µs N/A
🆕 test_claude_code_variant_detection N/A 536.7 µs N/A
🆕 test_generic_ai_catch_all N/A 164.8 µs N/A
🆕 test_empty_message_passes N/A 746.1 µs N/A
🆕 test_forbid_policy_allows_clean_commit N/A 177.5 µs N/A
🆕 test_forbid_policy_multiple_tools N/A 299.3 µs N/A
🆕 test_forbid_policy_rejects_ai_commit N/A 273.8 µs N/A
🆕 test_ignore_policy_always_passes N/A 132.4 µs N/A
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.


Comparing feature/ai-attribution-governance (48d9a17) with main (72ab7d3)2

Open in CodSpeed

Footnotes

  1. 109 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (9b303d9) during the generation of this report, so 72ab7d3 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

P0 fixes:
- Template leak in text output: _record_failure now passes dynamic
  error/suggest into the rule_dict before printing.
- Dual validator conflict: ai_attribution handles only 'forbid';
  ai_trailer_style handles only 'require'.  One policy = one check.

P1 fixes:
- Kernel Assisted-by format now accepts optional trailing tool list.
- Generic model names now match claude-sonnet-4, gpt-4-turbo, etc.
- ALL_PATTERNS tuples include 'kind' field (fixes body_marker
  misclassification as trailer).
- find_co_authored_by_ai deduplicates via seen set.
- Claude regex anchors to noreply@anthropic.com / GitHub noreply
  to avoid false positives with human co-authors named Claude.
- Copilot regex anchored to its specific GitHub noreply address.
- Added aider '(aider)' author suffix pattern.
- Removed dead github-actions[bot] pattern.
- Updated tests: capsys output checks, human-name false-positive
  regression tests, kernel format fixtures.
Simplify AI attribution to a single binary choice:
  ai_attribution = "ignore" | "forbid" (default: "ignore")

Rationale:
- Different AI tools generate different trailer styles (Co-authored-by
  vs Assisted-by vs future formats).  Enforcing a single style is not
  commit-check's job -- 'forbid' already rejects all known formats.
- 'require' mode gave a false sense of security (can't detect
  undisclosed AI usage) and required users to pick a style side.

Changes:
- Remove AiTrailerStyleValidator class and all its references
- Remove ai_trailer_style TOML key, CLI arg, env var, and default
- Remove require from --ai-attribution choices (only ignore|forbid)
- Remove ai_trailer_style from rules_catalog, config_merger, api,
  main.py, engine VALIDATOR_MAP, and all related tests
- AiAttributionValidator now handles only forbid (single responsibility)
- Simplify rule_builder: only builds ai_attribution rule for forbid
- configuration.rst: add ai_attribution to example config, env var
  table, CLI mapping, and options reference table
- README.rst: mention AI attribution in overview and add config
  example with ai_attribution = "forbid"
- what-is-new.rst: add v2.10.0 entry describing the AI attribution
  governance feature and the full list of detected tools
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 5, 2026
@shenxianpeng shenxianpeng changed the title feat: add AI attribution governance (forbid/require/ignore with signature database) feat: add AI attribution governance (forbid known AI tool signatures) Jul 5, 2026
@shenxianpeng shenxianpeng added the minor A minor version bump label Jul 5, 2026
Follow imperatives.py pattern — ai_signatures_data.py holds tool
definitions; ai_signatures.py holds detection API only.
@shenxianpeng shenxianpeng removed documentation Improvements or additions to documentation tests Add test related changes labels Jul 6, 2026
The generic AI model-name pattern matched any Co-authored-by trailer whose
value started with claude/gpt/gemini plus any email, so a human co-author
with a bare first name such as "Claude" would be flagged incorrectly.
Require a hyphenated model suffix (e.g. claude-sonnet-4) so bare human
names are never matched while real model identifiers still are.

Also remove the now-unused find_co_authored_by_ai and
find_assisted_by_trailers helpers left over from the dropped trailer-style
mode, fix the stale AiAttributionValidator docstring, and add regression
tests covering human-name false positives and model-identifier detection.
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Add test related changes labels Jul 6, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@shenxianpeng shenxianpeng removed documentation Improvements or additions to documentation tests Add test related changes labels Jul 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
commit_check/rule_builder.py (1)

254-271: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalid/typo ai_attribution config values silently behave as "ignore".

Any value other than "forbid" (e.g. a typo like "forbidd" from cchk.toml or an env var) falls through to return None, silently disabling the check with no warning. The CLI mitigates this via choices=["ignore", "forbid"], but TOML/env config paths aren't similarly constrained. A misconfigured project believes AI-attribution enforcement is active when it is not.

This is the same class of issue flagged in an earlier review (previously scoped to the 3-value ignore/forbid/require model); it still applies to the now-simplified binary model.

🐛 Proposed fix to fail loudly on invalid policy values
     def _build_ai_attribution_rule(
         self, catalog_entry: RuleCatalogEntry
     ) -> ValidationRule | None:
         """Build AI attribution validation rule.
 
         Only active when policy is ``"forbid"`` — rejects any commit with
         known AI tool signatures.
         """
         policy = self.commit_config.get("ai_attribution", DEFAULT_AI_ATTRIBUTION)
-        if policy != "forbid":
+        if policy not in ("ignore", "forbid"):
+            raise ValueError(
+                f"Invalid ai_attribution policy {policy!r}; expected 'ignore' or 'forbid'"
+            )
+        if policy != "forbid":
             return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/rule_builder.py` around lines 254 - 271, The ai_attribution
policy handling in _build_ai_attribution_rule currently treats any non-"forbid"
value as disabled, so typos from commit_config can silently skip enforcement.
Update the policy lookup and ValidationRule creation path to explicitly accept
only the supported values ("ignore" and "forbid"), and make invalid values fail
loudly instead of returning None. Use _build_ai_attribution_rule, commit_config,
and DEFAULT_AI_ATTRIBUTION as the key places to adjust validation and error
handling.
tests/ai_signatures_test.py (1)

177-186: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test can pass vacuously if body-marker detection breaks.

If detect_ai_signatures returns an empty list (e.g., the body-marker pattern regresses), the for r in result: loop never executes and the test passes without exercising the assertion at all — the same class of issue as the previously-fixed tautological assertion.

🐛 Proposed fix
     def test_kind_field_correct_for_body_marker(self):
         """Body markers have kind='body_marker', not 'trailer'."""
         message = "feat: add feature\n\nGenerated by AI"
         result = detect_ai_signatures(message)
+        assert len(result) >= 1, "Expected at least one match for 'Generated by AI'"
         for r in result:
             if r["description"].startswith("``Generated by AI"):
                 assert r["kind"] == "body_marker", (
                     f"Expected body_marker, got {r['kind']}"
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ai_signatures_test.py` around lines 177 - 186, The test in
detect_ai_signatures is vulnerable to vacuous success because it only asserts
inside the loop over result, so an empty list never exercises the body-marker
check. Update test_kind_field_correct_for_body_marker to first assert that
detect_ai_signatures(message) returns at least one matching body-marker entry
(or otherwise fail when none is found), then verify that the matching item’s
kind is body_marker using the existing detect_ai_signatures helper and the
description prefix check.
🧹 Nitpick comments (1)
tests/ai_signatures_test.py (1)

271-271: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename unused loop variables per Ruff hint.

Static analysis flags regex and kind as unused in this loop body.

🧹 Proposed fix
-        for regex, tool_name, desc, kind in ALL_PATTERNS:
+        for _regex, tool_name, desc, _kind in ALL_PATTERNS:
             assert desc, f"Pattern for {tool_name} is missing a description"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ai_signatures_test.py` at line 271, The loop over ALL_PATTERNS in the
AI signatures test has unused bindings for regex and kind, which Ruff flags.
Update the tuple unpacking in the relevant test loop to use placeholder names
for the unused values while keeping tool_name and desc as-is, so the intent is
clear and the warning is removed.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@commit_check/ai_signatures_data.py`:
- Around line 159-169: The DEVIN signature is too broad because the current
Co-authored-by matcher in ai_signatures_data.py accepts any person named Devin,
so tighten the KnownAiTool pattern to match Devin’s specific bot identity/email
instead of a bare name. Update the DEVIN entry and its _trailer matcher so only
the intended bot co-author format is recognized, and ensure human co-authors
named Devin no longer match.

---

Duplicate comments:
In `@commit_check/rule_builder.py`:
- Around line 254-271: The ai_attribution policy handling in
_build_ai_attribution_rule currently treats any non-"forbid" value as disabled,
so typos from commit_config can silently skip enforcement. Update the policy
lookup and ValidationRule creation path to explicitly accept only the supported
values ("ignore" and "forbid"), and make invalid values fail loudly instead of
returning None. Use _build_ai_attribution_rule, commit_config, and
DEFAULT_AI_ATTRIBUTION as the key places to adjust validation and error
handling.

In `@tests/ai_signatures_test.py`:
- Around line 177-186: The test in detect_ai_signatures is vulnerable to vacuous
success because it only asserts inside the loop over result, so an empty list
never exercises the body-marker check. Update
test_kind_field_correct_for_body_marker to first assert that
detect_ai_signatures(message) returns at least one matching body-marker entry
(or otherwise fail when none is found), then verify that the matching item’s
kind is body_marker using the existing detect_ai_signatures helper and the
description prefix check.

---

Nitpick comments:
In `@tests/ai_signatures_test.py`:
- Line 271: The loop over ALL_PATTERNS in the AI signatures test has unused
bindings for regex and kind, which Ruff flags. Update the tuple unpacking in the
relevant test loop to use placeholder names for the unused values while keeping
tool_name and desc as-is, so the intent is clear and the warning is removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba564f90-942a-4ef0-9300-09c0c4d01e29

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9a4e2 and 48d9a17.

📒 Files selected for processing (15)
  • README.rst
  • commit_check/__init__.py
  • commit_check/ai_signatures.py
  • commit_check/ai_signatures_data.py
  • commit_check/api.py
  • commit_check/config_merger.py
  • commit_check/engine.py
  • commit_check/main.py
  • commit_check/rule_builder.py
  • commit_check/rules_catalog.py
  • docs/configuration.rst
  • docs/what-is-new.rst
  • tests/ai_signatures_test.py
  • tests/engine_test.py
  • tests/rule_builder_test.py
💤 Files with no reviewable changes (3)
  • commit_check/api.py
  • commit_check/config_merger.py
  • tests/engine_test.py
✅ Files skipped from review due to trivial changes (2)
  • docs/configuration.rst
  • docs/what-is-new.rst

Comment on lines +159 to +169
# --- Devin ---
DEVIN = KnownAiTool(
name="Devin",
patterns=[
_trailer(
"Co-authored-by",
r"Devin\s*(?:<[^>]*>)?",
"``Co-authored-by: Devin`` trailer",
),
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Devin AI Cognition commit co-authored-by bot email address

💡 Result:

Devin AI, developed by Cognition, uses a Co-authored-by trailer in its git commit messages to provide attribution for changes made by the AI agent [1][2]. Regarding the email address associated with this trailer, official documentation for Devin notes a correction in its changelog: the system was updated to use the correct GitHub App bot email address instead of the previously used noreply@cognition.ai address [3]. The standard format used for the trailer is: Co-authored-by: Devin devin@cognition.ai While earlier or alternative configurations may have utilized other patterns, current documentation emphasizes the transition to official GitHub App bot integrations for improved attribution and compatibility [3]. Depending on the specific integration setup (such as the Devin GitHub App), the trailer may reference the official GitHub App bot identity (e.g., devin-ai-integration[bot]) to ensure clear provenance within repository histories [4]. Users are encouraged to check their specific repository's Devin integration documentation for the most accurate, environment-specific attribution string [5][3].

Citations:


Restrict DEVIN to a bot identity

Devin\s*(?:<[^>]*>)? still matches any human co-author named Devin with any email, so Co-authored-by: Devin <devin@company.com> is classified as AI and rejected in forbid mode. Anchor this to Devin’s bot email/identity instead of a bare name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/ai_signatures_data.py` around lines 159 - 169, The DEVIN
signature is too broad because the current Co-authored-by matcher in
ai_signatures_data.py accepts any person named Devin, so tighten the KnownAiTool
pattern to match Devin’s specific bot identity/email instead of a bare name.
Update the DEVIN entry and its _trailer matcher so only the intended bot
co-author format is recognized, and ensure human co-authors named Devin no
longer match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request minor A minor version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant