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
4 changes: 3 additions & 1 deletion docs/blog/posts/ai-commit-attribution-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ sign-off.

The detection covers co-author and sign-off lines that name Claude Code, GitHub
Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby or a model, and
vendor marks such as "Generated with Claude Code". It matches the tools'
vendor marks such as "Generated with Claude Code";
[v2.19.0](../../changelog.md#v2190) added Amp, OpenHands, Factory Droid and
Jules. It matches the tools'
noreply addresses, so a human co-author who happens to be called Claude is left
alone. [CC013](../../rules.md#cc013) keeps the current list.

Expand Down
48 changes: 48 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ below and to the page that documents the feature properly.

| Version | What changed | Documented in |
|---|---|---|
| [2.19.0](#v2190) | Branch descriptions held to the Conventional Branch grammar; Amp, OpenHands, Factory Droid and Jules recognised | [CC201](rules.md#cc201) · [CC013](rules.md#cc013) |
| [2.18.3](#v2183) | `--fix` corrects a message in place; `--branch` reads GitLab, Bitbucket, Azure and Jenkins variables | [Fixing a message in place](example.md#fixing-a-message-in-place) · [Other CI](guides/other-ci.md) |
| [2.18.0](#v2180) | AI disclosure policy: three rules, and the correction written for you | [Policy guides](guides/policies.md#asking-for-disclosure-instead) · [CC014](rules.md#cc014) |
| [2.17.0](#v2170) | Concrete corrections for mechanical slips; a `warn` level per rule | [Reading the JSON](example.md#reading-the-json) · [Report without enforcing](configuration.md#report-a-rule-without-enforcing-it) |
Expand All @@ -30,6 +31,53 @@ 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.19.0 (2026-10-14) { #v2190 }

### Added

* **`require_description_grammar` checks what follows the branch type** —
`conventional_branch` checks the `<type>/` prefix and accepted anything
after it, so `feature/Add_Login`, `fix/header_bug` and `release/v1.-2.0`
all passed. With `[branch] require_description_grammar = true` the
description must follow the specification's grammar: lowercase letters and
digits, words joined by single hyphens, dots only inside a word. It is off
by default, because bot branches such as
`dependabot/npm_and_yarn/lodash-4.17.21` do not follow that grammar.
See PR [#581](https://github.com/commit-check/commit-check/pull/581) and
[CC201](rules.md#cc201).

* **Amp, OpenHands, Factory Droid and Jules are recognised** — co-author and
sign-off lines naming them (`Amp <amp@ampcode.com>`, `openhands`,
`factory-droid[bot]`, `google-labs-jules[bot]`) and Amp's
`Amp-Thread-ID:` trailer now count as AI attribution, for
[CC013](rules.md#cc013) and for the `disclose` rules alike. People named
Jules or Amp are not flagged.
See PR [#606](https://github.com/commit-check/commit-check/pull/606) and
[Forbidding AI-attributed commits](guides/policies.md#forbidding-ai-attributed-commits).

* **`--format json` says why a check was skipped** — a check skipped because
the author or a co-author is on an `ignore_authors` list carries a `reason`,
such as `author dependabot[bot] is in [commit].ignore_authors`. Every check
has the key; it is empty when there is no reason to give.
See PR [#605](https://github.com/commit-check/commit-check/pull/605) and
[When a check is skipped](example.md#when-a-check-is-skipped).

### Fixed

* **`check-tag` checks the tags a push sends** — under pre-commit the hook
judged the tags on `HEAD`, so a bad tag sent by `git push --follow-tags`
from another branch got through, and a tag push could be rejected over a
local tag on `HEAD` that it did not carry. It now checks the tag a tag push
names, and on a branch push the tags on every commit the push carries.
See PR [#603](https://github.com/commit-check/commit-check/pull/603).

* **A GitHub Actions tag build has no branch to judge** — on a tag push,
`GITHUB_REF_NAME` holds the tag, so `--branch` failed
[CC201](rules.md#cc201) on `v1.2.0`. A tag build now falls back to `HEAD`,
which is always accepted, as tag builds on GitLab, Bitbucket, Azure and
Jenkins already did.
See PR [#603](https://github.com/commit-check/commit-check/pull/603).

## v2.18.3 (2026-10-07) { #v2183 }

### Added
Expand Down
15 changes: 10 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,13 @@ allow_force_push = true # set false to block force pushes
[branch]
# https://conventionalbranch.org
conventional_branch = true
# changed: spec types only. The default is a superset — these plus the
# Conventional Commit types, AI agent prefixes and bot prefixes — so setting
# this at all narrows it. Omit the line to accept all of them.
# changed: the spec's five purpose types only. The default is a superset —
# these plus their aliases feat and fix, AI agent prefixes, the other
# Conventional Commit types and bot prefixes — so setting this at all narrows
# it. Omit the line to accept all of them.
allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"]
# true also holds the part after "/" to the spec's grammar
require_description_grammar = false
# allow_branch_names = [] # optional: extra standalone names, e.g. ["develop"]
# require_rebase_target = "main" # optional: no rebase requirement by default
# ignore_authors = [] # optional: as above, for branch checks
Expand Down Expand Up @@ -261,7 +264,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.18.3
rev: v2.19.0
hooks:
- id: check-message
args:
Expand Down Expand Up @@ -306,6 +309,7 @@ The full mapping between the three forms:
| `conventional_branch = true` | `CCHK_CONVENTIONAL_BRANCH=true` | `--conventional-branch=true` |
| `allow_branch_types = ["feature"]` | `CCHK_ALLOW_BRANCH_TYPES=feature,bugfix` | `--allow-branch-types=feature,bugfix` |
| `allow_branch_names = ["develop"]` | `CCHK_ALLOW_BRANCH_NAMES=develop,staging` | `--allow-branch-names=develop,staging` |
| `require_description_grammar = true` | `CCHK_REQUIRE_DESCRIPTION_GRAMMAR=true` | `--require-description-grammar=true` |
| `require_rebase_target = "main"` | `CCHK_REQUIRE_REBASE_TARGET=main` | `--require-rebase-target=main` |
| `allow_force_push = true` | `CCHK_ALLOW_FORCE_PUSH=true` | `--no-force-push` (sets `allow_force_push` to `false`) |
| `ai_attribution = "forbid"` | `CCHK_AI_ATTRIBUTION=forbid` | `--ai-attribution=forbid` |
Expand Down Expand Up @@ -371,7 +375,8 @@ the same thing twice, so read the description rather than the cell:
| commit | ai_disclosure_trailers | list[str] | ["Assisted-by", "Generated-by"] | The trailers that disclose AI assistance under `"disclose"`. The first one is what a correction is written with. Listing `Co-authored-by` says the project accepts the tool as a co-author, and CC015 then passes. `Signed-off-by` is refused: only a person can certify the DCO. |
| commit | ai_disclosure_pattern | str | "" (any value) | A regex the disclosure trailer's value must match under `"disclose"`, e.g. `'^\S+/\S+$'` for an `agent/model` format. Empty accepts any value, but a trailer with no value at all is reported whatever the pattern is. |
| branch | conventional_branch | bool | true | Enforce Conventional Branch specification. |
| branch | allow_branch_types | list[str] | ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] | Allowed branch types when `conventional_branch` is true. The default is a superset of the [Conventional Branch spec](https://conventionalbranch.org/): the spec types (`feature`, `bugfix`, `hotfix`, `release`, `chore`) plus the Conventional Commit types (`build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test`), AI agent prefixes (`ai`, `claude`, `codex`, `copilot`, `cursor`) and bot prefixes (`dependabot`, `renovate`). For strict spec-only validation, set this option explicitly (e.g. `["feature", "bugfix", "hotfix", "release", "chore"]`). |
| branch | allow_branch_types | list[str] | ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] | Allowed branch types when `conventional_branch` is true. The default is a superset of the [Conventional Branch spec](https://conventionalbranch.org/): the spec's types (`feature`, `bugfix`, `hotfix`, `release`, `chore`, their aliases `feat` and `fix`, and the AI agent prefixes `ai`, `claude`, `codex`, `copilot`, `cursor`) plus the other Conventional Commit types (`build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test`) and bot prefixes (`dependabot`, `renovate`). For strict spec-only validation, set this option explicitly to the spec's types; [CC201](rules.md#cc201) has the full config. |
| branch | require_description_grammar | bool | false | Also hold the description after `<type>/` to the Conventional Branch grammar: lowercase letters and digits, words joined by single hyphens, dots only inside a word. Off by default, because bot branches such as `dependabot/npm_and_yarn/lodash-4.17.21` do not follow it. See [CC201](rules.md#cc201). |
| branch | allow_branch_names | list[str] | [] (empty list) | Additional standalone branch names allowed when conventional_branch is true (e.g., ["develop", "staging"]). By default, master, main, HEAD, and PR-* are always allowed. |
| branch | require_rebase_target | str | "" (no requirement) | Target branch for rebase requirement. If not set, no rebase validation is performed. |
| push | allow_force_push | bool | true | Allow force pushes. Set to `false` to block force pushes when used as a pre-push hook or with `--no-force-push`. |
Expand Down
17 changes: 15 additions & 2 deletions docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pushed:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.18.3
rev: v2.19.0
hooks:
- id: check-no-force-push
stages: [pre-push]
Expand Down Expand Up @@ -286,6 +286,18 @@ $ echo "Merge branch 'main' into topic" | commit-check -m --no-banner
```

In JSON each skipped check carries `"status": "skip"`, distinct from `"pass"`.
When a check is skipped because the author or a co-author is on an
`ignore_authors` list, `reason` says so. Here `HEAD` is a Dependabot commit
and `cchk.toml` lists `dependabot[bot]` under `[commit] ignore_authors`:

```console
$ commit-check -m --rev HEAD --format json | jq -c '.checks[] | {rule_id, status, reason}'
{"rule_id":"CC001","status":"skip","reason":"author dependabot[bot] is in [commit].ignore_authors"}
{"rule_id":"CC004","status":"skip","reason":"author dependabot[bot] is in [commit].ignore_authors"}
{"rule_id":"CC005","status":"skip","reason":"author dependabot[bot] is in [commit].ignore_authors"}
```

For any other skip, such as a merge subject, `reason` is empty.

!!! warning "A green run can still have validated nothing"

Expand Down Expand Up @@ -323,7 +335,8 @@ $ echo "Fix: add streaming support" | commit-check -m --format json | jq '.check
"error": "The commit message should follow Conventional Commits. See https://www.conventionalcommits.org",
"suggest": "Use \"fix: add streaming support\"",
"fix": "fix: add streaming support",
"docs_url": "https://commit-check.com/rules/#cc001"
"docs_url": "https://commit-check.com/rules/#cc001",
"reason": ""
}
```

Expand Down
11 changes: 8 additions & 3 deletions docs/guides/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,18 @@ 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 and Tabby, the
disclosure trailers `Assisted-by:` and `Generated-by:`, vendor marks such as
`🤖 Generated with Claude Code`, and generic AI model names. This is the
GitHub Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, Amp,
OpenHands, Factory Droid and Jules, the disclosure trailers `Assisted-by:` and
`Generated-by:`, vendor marks such as `🤖 Generated with Claude Code` and
Amp's `Amp-Thread-ID:`, and generic AI model names. This is the
position [Kubernetes](https://www.kubernetes.dev/docs/guide/pull-requests/)
takes — no AI trailers in the history, disclosure in the pull request
instead.

These rules read only the message. Jules and OpenHands can commit under their
own bot identity instead of adding a co-author line, and such a commit passes
when its message carries no trailer.
Comment thread
shenxianpeng marked this conversation as resolved.

!!! warning "This checks disclosure, not authorship"

These rules read commit metadata. They detect a commit that *says* it
Expand Down
10 changes: 7 additions & 3 deletions docs/guides/pre-commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.18.3
rev: v2.19.0
hooks:
- id: check-message
- id: check-branch
Expand Down Expand Up @@ -50,6 +50,10 @@ The three `pre-push` hooks need their own install too:
$ pre-commit install --hook-type pre-push
```

`check-tag` checks the tags the push sends: the tag a tag push names, and on
a branch push, such as `git push --follow-tags`, the tags on every commit the
Comment thread
shenxianpeng marked this conversation as resolved.
push carries.

pre-commit runs no hook for a push that creates a ref without carrying new
commits. So a new tag pushed on its own, when the commit it points at is
already on the remote, is never seen by `check-tag`.
Expand All @@ -61,7 +65,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.18.3
rev: v2.19.0
hooks:
- id: check-message
args:
Expand All @@ -83,7 +87,7 @@ colon, and lets the commit through:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.18.3
rev: v2.19.0
hooks:
- id: check-message
args: [--fix]
Expand Down
4 changes: 3 additions & 1 deletion docs/guides/python-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Every function returns the same shape:
"suggest": "...", # advice for a person
"fix": "...", # the corrected value, or "" when it takes judgment
"docs_url": "https://commit-check.com/rules/#cc001",
"reason": "...", # why a skipped check did not run, when known; else ""
},
],
}
Expand All @@ -80,7 +81,8 @@ Every function returns the same shape:
- **`skip` is not `pass`.** A check skips when it never ran — the author is in
`ignore_authors`, or there was nothing to check — and the top-level `status`
is `skip` only when every check skipped. A skipped run validated nothing, so
do not read it as approval.
do not read it as approval. When the author or a co-author is in
`ignore_authors`, the check's `reason` names them.
- **`warn`** is a rule listed under the config's `warn`: reported in full,
never a failure. The top-level `status` stays `pass`, and `warnings` counts
them.
Expand Down
41 changes: 34 additions & 7 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -537,9 +537,10 @@ 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 or Tabby, a disclosure trailer such as `Assisted-by:` or
`Generated-by:`, a vendor's own mark such as `🤖 Generated with Claude Code`,
and generic AI model names.
Aider, Windsurf, Tabby, Amp, OpenHands, Factory Droid or Jules, a disclosure
trailer such as `Assisted-by:` or `Generated-by:`, a vendor's own mark such
as `🤖 Generated with Claude Code` or Amp's `Amp-Thread-ID:` trailer, and
generic AI model names.

**Why is this bad?**

Expand Down Expand Up @@ -762,6 +763,11 @@ Checks that the current branch name follows the

`master`, `main`, `HEAD`, and `PR-*` are always accepted.

By default only the type is checked, and any description after the `/` is
accepted. With `require_description_grammar` the description has to follow
the specification's grammar too: lowercase letters and digits, words joined
by single hyphens, dots only inside a word, as in `release/v1.2.0`.

**Why is this bad?**

A predictable prefix is something automation can act on: CI can skip expensive
Expand All @@ -787,14 +793,35 @@ feature/role-caching

* `branch.conventional_branch` — set to `false` to disable this rule.
* `branch.allow_branch_types` — the accepted `<type>` values. The default is
a superset of the specification: the spec types plus the Conventional Commit
types, AI agent prefixes (`ai`, `claude`, `codex`, `copilot`,
`cursor`), and bot prefixes (`dependabot`, `renovate`). Set it
explicitly for strict spec-only validation.
a superset of the specification: the spec's types (`feature`, `bugfix`,
`hotfix`, `release`, `chore`, their aliases `feat` and `fix`, and the AI
agent prefixes `ai`, `claude`, `codex`, `copilot` and `cursor`), plus the
other Conventional Commit types and the bot prefixes `dependabot` and
`renovate`. Set it explicitly for strict spec-only validation.
* `branch.require_description_grammar` — set to `true` to hold the
description to the specification's grammar, so `feature/Add_Login`,
`feature/new--login` and `feature/a/b` are rejected. Off by default,
because bot branches such as `dependabot/npm_and_yarn/lodash-4.17.21` do
not follow it.
* `branch.allow_branch_names` — additional standalone names to accept, such as
`["develop", "staging"]`.
* `branch.ignore_authors` — bypass the check for specific authors.

To check branches exactly as the specification does, set the spec's types,
the description grammar, and its third trunk branch, `develop`:

```toml title="cchk.toml"
[branch]
conventional_branch = true
allow_branch_types = ["feature", "feat", "bugfix", "fix", "hotfix", "release", "chore", "ai", "claude", "codex", "copilot", "cursor"]
require_description_grammar = true
allow_branch_names = ["develop"]
```

With that, `commit-check --branch` agrees with every case of the
specification's [conformance suite](https://github.com/conventional-branch/conventional-branch/blob/main/tests/fixtures.json)
that git can create as a branch.

### merge-base (CC202) { #cc202 }

**What it does**
Expand Down