diff --git a/docs/changelog.md b/docs/changelog.md index 85bdf0a..9e48953 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,7 @@ below and to the page that documents the feature properly. | Version | What changed | Documented in | |---|---|---| +| [2.20.0](#v2200) | A commit authored by an AI tool rejected (CC017); Continue, Mistral Vibe and Qwen Code recognised; subject length `0` turns the rule off; Python 3.10 dropped | [CC017](rules.md#cc017) · [CC013](rules.md#cc013) · [CC004](rules.md#cc004) | | [2.19.5](#v2195) | GitHub merge queue branches accepted by default; aider's default co-author trailer recognised; exit 2 when git cannot read the repository | [CC201](rules.md#cc201) · [CC013](rules.md#cc013) · [Exit codes](troubleshoot.md#exit-codes) | | [2.19.4](#v2194) | `agents/` branches accepted by default; Cursor's `Made-with: Cursor` trailer recognised | [CC201](rules.md#cc201) · [CC013](rules.md#cc013) | | [2.19.3](#v2193) | A quoted `"false"` for a true/false setting refused instead of read as true | [Every option](configuration.md#every-option) | @@ -36,6 +37,48 @@ below and to the page that documents the feature properly. | [2.5.0](#v250) | Organization-wide config with `inherit_from` | [Across an organization](guides/organization.md) | | [2.0.0](#v200) | Configuration moved from YAML to TOML — breaking | [Migrating from v1](migration.md) | +## v2.20.0 (2026-11-08) { #v2200 } + +### Added + +* **[CC017](rules.md#cc017) rejects a commit whose author is an AI tool** — + the message rules read only the message, so an agent that commits under + its own identity (`claude[bot]`, `google-labs-jules[bot]`, + `devin-ai-integration[bot]`, OpenHands, an `(aider)` name) passed with a + clean message, even one that credits a person as co-author. Under + `ai_attribution = "forbid"` or `"disclose"` such a commit now fails. + `ignore_authors` skips it and `warn` can report it without failing. + See PR [#661](https://github.com/commit-check/commit-check/pull/661). + +* **[CC013](rules.md#cc013)–[CC016](rules.md#cc016) recognise Continue, + Mistral Vibe and Qwen Code** — their default commit signatures + (`Generated with [Continue](https://continue.dev)`, + `Generated by Mistral Vibe.` with `vibe@mistral.ai`, and + `Co-authored-by: Qwen-Coder `) passed every + policy. + See PRs [#657](https://github.com/commit-check/commit-check/pull/657) and + [#659](https://github.com/commit-check/commit-check/pull/659). + +### Changed + +* **`subject_max_length = 0` and `subject_min_length = 0` turn the rule + off** — `0` used to mean a limit of zero characters, so a project that + wanted only the AI check had to pick a large number instead. See + [Checking AI attribution and nothing else](guides/policies.md#checking-ai-attribution-and-nothing-else). + See PR [#660](https://github.com/commit-check/commit-check/pull/660). + +* **Python 3.11 or newer is required** — Python 3.10 reached end of life, and + the `tomli` dependency is gone with it. The GitHub Action needs it too from + v2.19.0: on `ubuntu-22.04`, add `actions/setup-python` first (see + [GitHub Action](guides/github-actions.md)). + See PR [#662](https://github.com/commit-check/commit-check/pull/662). + +### Fixed + +* **`[commit] require_verified` no longer warns as an unknown setting** — the + GitHub App reads it, and the CLI now leaves it alone. + See PR [#656](https://github.com/commit-check/commit-check/pull/656). + ## v2.19.5 (2026-11-04) { #v2195 } ### Fixed diff --git a/docs/compare/tools.md b/docs/compare/tools.md index 7926ad5..fd57038 100644 --- a/docs/compare/tools.md +++ b/docs/compare/tools.md @@ -65,7 +65,7 @@ Replace the hook: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-message ``` diff --git a/docs/configuration.md b/docs/configuration.md index 24f814b..7370bbf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -274,7 +274,7 @@ Used from a hook definition, with no config file anywhere in the repository: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-message args: @@ -387,8 +387,8 @@ the same thing twice, so read the description rather than the cell: | commit | message_pattern | str | "" (no custom pattern) | Custom regex pattern for commit message validation. When set, this pattern replaces the auto-generated Conventional Commits regex entirely, making it possible to enforce custom formats such as JIRA smart commits (e.g., `"^PROJ-\\d+: .+"`). When `message_pattern` is set (non-empty) it takes precedence over `conventional_commits`. | | commit | subject_capitalized | bool | false | Subject must start with a capital letter. | | commit | subject_imperative | bool | false | Subject must be in imperative mood. Judged on the first word's form, so a verb no list contains is still accepted — see [CC003](rules.md#cc003). | -| commit | subject_max_length | int | 80 | Maximum length of the subject line. | -| commit | subject_min_length | int | 5 | Minimum length of the subject line. | +| commit | subject_max_length | int | 80 | Maximum length of the subject line. `0` turns the check off. | +| commit | subject_min_length | int | 5 | Minimum length of the subject line. `0` turns the check off. | | commit | allow_commit_types | list[str] | ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "build", "ci"] | Allowed commit types when conventional_commits is true. | | commit | allow_merge_commits | bool | true | Allow merge commits. | | commit | allow_revert_commits | bool | true | Allow revert commits. | diff --git a/docs/example.md b/docs/example.md index 89d2739..db0ef23 100644 --- a/docs/example.md +++ b/docs/example.md @@ -162,7 +162,7 @@ pushed: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-no-force-push stages: [pre-push] diff --git a/docs/getting-started.md b/docs/getting-started.md index 17100cb..42698bc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -44,7 +44,7 @@ name. !!! tip "Supported Python versions" - Commit Check supports Python 3.10 through 3.15, on Linux, macOS and Windows. + Commit Check supports Python 3.11 through 3.15, on Linux, macOS and Windows. Running it as a [pre-commit hook](guides/pre-commit.md) or a [GitHub Action](guides/github-actions.md) needs no diff --git a/docs/guides/github-actions.md b/docs/guides/github-actions.md index 4c4f335..4a1a99d 100644 --- a/docs/guides/github-actions.md +++ b/docs/guides/github-actions.md @@ -39,6 +39,18 @@ jobs: The Action reads the same `cchk.toml` as the CLI, so a repository that already has one needs no Action-specific configuration. +!!! note "Python 3.11 or newer" + + The Action runs on the runner's own Python, and since v2.19.0 it needs + 3.11 or newer. The `*-latest` GitHub-hosted runners have it. + `ubuntu-22.04` ships 3.10, so add a setup step before the Action there: + + ```yaml + - uses: actions/setup-python@v7 + with: + python-version: "3.11" + ``` + ## Inputs | Input | Default | What it does | diff --git a/docs/guides/policies.md b/docs/guides/policies.md index 909649c..8b3fc3f 100644 --- a/docs/guides/policies.md +++ b/docs/guides/policies.md @@ -141,7 +141,8 @@ Docs: https://commit-check.com/rules/#cc013 Recognised signatures are co-author and sign-off lines naming Claude Code, GitHub Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, Amp, -OpenHands, Factory Droid and Jules, the disclosure trailers `Assisted-by:` and +OpenHands, Factory Droid, Jules, Continue, Mistral Vibe and Qwen Code, the +disclosure trailers `Assisted-by:` and `Generated-by:`, vendor marks such as `🤖 Generated with Claude Code`, Amp's `Amp-Thread-ID:`, the Copilot coding agent's `Agent-Logs-Url:` and Cursor's `Made-with: Cursor`, and generic AI model names. This is the @@ -166,6 +167,40 @@ also commits as its own account, but every commit it makes carries an mechanism against those who aren't. Be clear with yourself about which of those you are buying. +### Checking AI attribution and nothing else + +`ai_attribution` sits alongside the message rules that are on by default, so +`--message` still applies Conventional Commits and the subject length limits +(80 characters at most, 5 at least). A project that has its own commit style +and only wants the AI check turns those off: + +```toml title="cchk.toml" +[commit] +ai_attribution = "forbid" +conventional_commits = false +subject_max_length = 0 # 0 turns the length rule off +subject_min_length = 0 +``` + +Or, in the GitHub Action, without a config file: + +```yaml +- uses: commit-check/commit-check-action@v2 + env: + CCHK_AI_ATTRIBUTION: forbid + CCHK_CONVENTIONAL_COMMITS: false + CCHK_SUBJECT_MAX_LENGTH: 0 + CCHK_SUBJECT_MIN_LENGTH: 0 + with: + message: true + branch: false +``` + +Leave the length limits on and a long subject fails the job too. Dependency +bots write long ones: Renovate's `Update dependency +com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer to +v20261103` is 95 characters. + ### Asking for disclosure instead Most projects that allow AI assistance ask to be told about it. @@ -288,6 +323,47 @@ triagers judge each contribution by whether its author can explain it in their own words. A policy like that leaves nothing for commit-check to enforce, so keep `ai_attribution` at its default, `"ignore"`. +### Recipes from real projects + +Many projects enforce their AI policy with a hand-written grep step in CI. +Each policy below fits in a few lines of `cchk.toml`. Add +`conventional_commits = false` if your project does not use Conventional +Commits (see [Checking AI attribution and nothing else](#checking-ai-attribution-and-nothing-else)). + +| Project | Its rule for commits | `cchk.toml` | +|---|---|---| +| [Kubernetes](https://www.kubernetes.dev/docs/guide/pull-requests/) | No AI co-author, no AI sign-off, no `Assisted-by` | `ai_attribution = "forbid"` | +| [Linux kernel](https://docs.kernel.org/process/coding-assistants.html) | Every commit signed off by a person; an AI never signs off | `ai_attribution = "disclose"`, `require_signed_off_by = true`, top-level `warn = ["ai_disclosure"]` | +| Nextcloud | `Assisted-by: AGENT_NAME:MODEL_VERSION` on AI-assisted commits; an AI never signs off | `ai_attribution = "disclose"`, `ai_disclosure_pattern = '^[^:\s]+:\S+'` | +| [pip](https://github.com/pypa/pip/blob/main/AI_POLICY.md), [attrs](https://github.com/python-attrs/attrs/blob/main/.github/AI_POLICY.md) | No AI co-author; disclosure not required | `ai_attribution = "disclose"`, top-level `warn = ["ai_disclosure"]` | +| Fedora, FluxCD | Disclose with `Assisted-by:` | `ai_attribution = "disclose"`, `ai_disclosure_trailers = ["Assisted-by"]` | + +The Nextcloud recipe, in full: + +```toml title="cchk.toml" +[commit] +ai_attribution = "disclose" +ai_disclosure_pattern = '^[^:\s]+:\S+' # AGENT_NAME:MODEL_VERSION +``` + +`Assisted-by: ClaudeCode:claude-sonnet-4-6` passes. `Assisted-by: Claude Code` +fails CC014 because it names no model, and `Signed-off-by: Claude +` fails CC016. + +Some projects ask every commit to say whether AI was used, with +`Assisted-by: none` when it was not. `disclose` only asks when a commit shows +signs of AI, so pair it with a `message_pattern` that requires the trailer: + +```toml title="cchk.toml" +[commit] +ai_attribution = "disclose" +message_pattern = '(?ms).*^Assisted-by:[ \t]*\S' +``` + +`message_pattern` replaces the Conventional Commits check, so this recipe +cannot be combined with `conventional_commits = true`. A commit with no +`Assisted-by:` line fails CC001; `Assisted-by: none` passes. + ### Exempting automation Bots that legitimately carry AI trailers can be excluded: diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md index 58ec09b..5960d6b 100644 --- a/docs/guides/pre-commit.md +++ b/docs/guides/pre-commit.md @@ -8,7 +8,7 @@ Add Commit Check to `.pre-commit-config.yaml`: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-message - id: check-branch @@ -68,7 +68,7 @@ Options can be passed as hook arguments, which keeps everything in one file: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-message args: @@ -90,7 +90,7 @@ colon, and lets the commit through: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.19.5 + rev: v2.20.0 hooks: - id: check-message args: [--fix] diff --git a/docs/guides/python-api.md b/docs/guides/python-api.md index 43191cd..096d437 100644 --- a/docs/guides/python-api.md +++ b/docs/guides/python-api.md @@ -127,7 +127,7 @@ pass 1 To apply a repository's own policy, load its file and pass it along: ```python -import tomllib # on Python 3.10: import tomli as tomllib +import tomllib from commit_check.api import validate_message @@ -137,8 +137,7 @@ with open("cchk.toml", "rb") as f: result = validate_message("feat: add streaming support", config=config) ``` -On Python 3.10, `tomli` is already installed: commit-check depends on it -there. `inherit_from` is not followed this way; only the file's own keys +`inherit_from` is not followed this way; only the file's own keys apply. Every key is in the [configuration reference](../configuration.md). ## Fixing until it passes diff --git a/docs/index.md b/docs/index.md index fa5bb91..580684d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ description: Commit policy as code for teams and AI agents. Messages, branches, diff --git a/docs/rules.md b/docs/rules.md index c7ea444..7692f68 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -312,7 +312,7 @@ same root cause. **Options** * `commit.subject_max_length` — the limit, in characters. Defaults to `80`. - `50` and `72` are the other conventional choices. + `50` and `72` are the other conventional choices. `0` turns the rule off. ### subject-min-length (CC005) { #cc005 } @@ -341,6 +341,7 @@ fix: reject config files with a null inherit_from **Options** * `commit.subject_min_length` — the minimum, in characters. Defaults to `5`. + `0` turns the rule off. ### allow-merge-commits (CC006) { #cc006 } @@ -563,7 +564,8 @@ git commit --amend --signoff Rejects a commit whose message carries any AI attribution: a co-author or sign-off line naming Claude Code, Copilot, Codex, Gemini, Cursor, Devin, -Aider, Windsurf, Tabby, Amp, OpenHands, Factory Droid or Jules, a disclosure +Aider, Windsurf, Tabby, Amp, OpenHands, Factory Droid, Jules, Continue, +Mistral Vibe or Qwen Code, a disclosure trailer such as `Assisted-by:` or `Generated-by:`, a vendor's own mark such as `🤖 Generated with Claude Code`, Amp's `Amp-Thread-ID:` trailer, the `Agent-Logs-Url:` trailer the Copilot coding agent adds or Cursor's @@ -697,6 +699,49 @@ Suggest: Use "Assisted-by: Claude" in place of the AI sign-off, then sign off yo * `commit.ai_attribution` — set to `"disclose"` to enable this rule. +### ai-author (CC017) { #cc017 } + +**What it does** + +Under `ai_attribution = "forbid"` or `"disclose"`, rejects a commit whose +author is an AI tool: `claude[bot]`, `copilot-swe-agent[bot]`, +`google-labs-jules[bot]`, `devin-ai-integration[bot]`, OpenHands, a name +ending in `(aider)`, or any name at a tool's own address such as +`noreply@anthropic.com`. The author is read the same way as a +`Co-authored-by:` line, so the two never disagree about who is a tool. + +An existing commit is judged by the author it records. A message piped in, +or one the `commit-msg` hook checks before the commit exists, is judged by +the author git would record: `GIT_AUTHOR_NAME` and `GIT_AUTHOR_EMAIL` when +set, otherwise `user.name` and `user.email`. The GitHub Action passes each +pull request commit's own author this way. + +**Why is this bad?** + +The message rules read only the message. Several agents commit under their +own identity and add no trailer at all, or credit the person as the +co-author, so a commit made entirely by a tool can carry a clean message. +Putting the tool in the author's place is the strongest attribution there +is, and it is what GitHub shows as the contributor. + +**Example** + +```text +CC017 ai-author check failed ==> google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> +An AI tool is the commit author: Jules +Suggest: Author the commit yourself: git commit --amend --reset-author +``` + +The commit fails even with a human `Co-authored-by:` line. + +**Options** + +* `commit.ai_attribution` — set to `"forbid"` or `"disclose"` to enable this + rule. +* `commit.ignore_authors` — authors listed here are skipped, for a bot you + trust to commit on its own. +* Top-level `warn = ["ai_author"]` reports the rule without failing the run. + ## Author rules ### author-name (CC101) { #cc101 }