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
74 changes: 74 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.1](#v2191) | `develop` accepted by default; stricter merge-subject, fixup, WIP and sign-off checks | [CC201](rules.md#cc201) · [CC001](rules.md#cc001) · [CC009](rules.md#cc009) |
| [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) |
Expand All @@ -31,6 +32,79 @@ 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.1 (2026-10-16) { #v2191 }

### Changed

* **`develop` is accepted by default** — the Conventional Branch
specification counts `develop` as a trunk branch beside `main` and
`master`, but [CC201](rules.md#cc201) failed it unless
`allow_branch_names` listed it. It now passes like `main` and `master`, and
the spec-strict config needs only the types and the description grammar.
This only widens what passes.
See PR [#612](https://github.com/commit-check/commit-check/pull/612).

* **Only merge subjects a tool wrote are exempt** — any subject starting with
`Merge ` skipped [CC001](rules.md#cc001) and the subject rules. Now only
the forms git, GitHub, GitLab and Bitbucket write are exempt, such as
`Merge branch 'x' into 'y'` and `Merge pull request #12 from …`. A typed
subject such as `Merge the parser rewrite` is judged like any other, so it
now fails CC001.
See PR [#619](https://github.com/commit-check/commit-check/pull/619).

* **A commit message file is checked as git will store it** — the
`commit-msg` hook runs before git strips comments, so git's comment block,
and with `git commit -v` the whole diff, were read as part of the message.
Comment lines and everything from the scissors line on are now dropped
first. A subject-only commit written in the editor now fails
[CC011](rules.md#cc011) when `require_body` is on, and a `Signed-off-by:`
line in the diff no longer counts. `--fix` keeps the comment and diff
blocks in the file.
See PR [#618](https://github.com/commit-check/commit-check/pull/618).

### Fixed

* **[CC009](rules.md#cc009) rejects `squash!` and `amend!` too** — with
`allow_fixup_commits = false` only `fixup!` commits failed.
See PR [#613](https://github.com/commit-check/commit-check/pull/613).

* **[CC010](rules.md#cc010) catches every WIP marker** — `WIP` followed by a
newline, a tab or a dash passed. Any subject that begins with the word `WIP`
or `[WIP]`, in any case, now fails; `wipe the cache` still passes.
See PR [#616](https://github.com/commit-check/commit-check/pull/616).

* **[CC012](rules.md#cc012) reads only a real trailer** — `Signed-off-by:`
text anywhere in the message passed, including the subject and a line with
no name. The check now needs a trailer line with a name and an address, and
it runs in linear time on any input.
See PR [#621](https://github.com/commit-check/commit-check/pull/621).

* **[CC002](rules.md#cc002) and [CC003](rules.md#cc003) skip only a real type
prefix** — any leading `word:` was skipped, even with
`conventional_commits = false`, so `Added: support for X` passed the
imperative check. The prefix is now skipped only when Conventional Commits
is on and the type is allowed.
See PR [#622](https://github.com/commit-check/commit-check/pull/622).

* **[CC013](rules.md#cc013) sees two more AI co-author forms** — a trailer
with a blank before the colon, `Co-authored-by : Claude <…>`, which git
still reads as a trailer, and the line Cursor's agent writes,
`Co-authored-by: Cursor Agent <cursoragent@cursor.com>`.
See PR [#623](https://github.com/commit-check/commit-check/pull/623).

* **The config is found from a subdirectory** — run below the top of a
repository, commit-check found no config and used the built-in defaults
without saying so. It now falls back to the top of the work tree, and a
relative `inherit_from` is read next to the config that names it.
See PR [#625](https://github.com/commit-check/commit-check/pull/625) and
[Where the config file lives](configuration.md#where-the-config-file-lives).

* **A parent config is read up to 1 MiB** — `inherit_from` now refuses a
larger parent, or a local path that is not a regular file, and continues
with the local config as it does when a parent is unreachable.
See PR [#614](https://github.com/commit-check/commit-check/pull/614) and
[Across an organization](guides/organization.md).

## v2.19.0 (2026-10-14) { #v2190 }

### Added
Expand Down
15 changes: 10 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ Check searches four locations and uses the first that exists:
3. `.github/cchk.toml`
4. `.github/commit-check.toml`

Run from a subdirectory of a repository, where none of the four is in the
current directory, it looks in the same four places at the top of the
repository's work tree. The current directory still comes first, so a package
in a monorepo can keep a config of its own.

Pass `--config` to point at one directly and skip the search:

```console
Expand Down Expand Up @@ -229,7 +234,7 @@ conventional_branch = true
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"]
# allow_branch_names = [] # optional: extra standalone names, e.g. ["staging"]
# require_rebase_target = "main" # optional: no rebase requirement by default
# ignore_authors = [] # optional: as above, for branch checks
```
Expand Down Expand Up @@ -264,7 +269,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.0
rev: v2.19.1
hooks:
- id: check-message
args:
Expand Down Expand Up @@ -308,7 +313,7 @@ The full mapping between the three forms:
| `author_name_pattern=^.+ .+$` | `CCHK_AUTHOR_NAME_PATTERN=^.+ .+$` | `--author-name-pattern=^.+ .+$` |
| `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` |
| `allow_branch_names = ["staging", "production"]` | `CCHK_ALLOW_BRANCH_NAMES=staging,production` | `--allow-branch-names=staging,production` |
| `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`) |
Expand Down Expand Up @@ -364,7 +369,7 @@ the same thing twice, so read the description rather than the cell:
| commit | allow_merge_commits | bool | true | Allow merge commits. |
| commit | allow_revert_commits | bool | true | Allow revert commits. |
| commit | allow_empty_commits | bool | true | Allow empty commits. |
| commit | allow_fixup_commits | bool | true | Allow fixup commits (e.g., "fixup! <commit message>"). |
| commit | allow_fixup_commits | bool | true | Allow fixup commits: `fixup!`, `squash!` and `amend!` subjects. |
| commit | allow_wip_commits | bool | true | Allow work-in-progress commits (e.g., "WIP: <commit message>"). |
| commit | require_body | bool | false | Require a body in the commit message. |
| commit | ignore_authors | list[str] | [] (none ignored) | List of commit authors **or co-authors** (`Co-authored-by:` lines) to bypass all commit checks. Useful for bots (e.g., `"dependabot[bot]"`, `"coderabbitai[bot]"`). |
Expand All @@ -377,7 +382,7 @@ the same thing twice, so read the description rather than the cell:
| 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'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 | allow_branch_names | list[str] | [] (empty list) | Additional standalone branch names allowed when conventional_branch is true (e.g., ["staging", "production"]). By default, main, master, develop, 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`. |
| branch | ignore_authors | list[str] | [] (none ignored) | List of authors to ignore (i.e., always allow). |
Expand Down
10 changes: 6 additions & 4 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.19.0
rev: v2.19.1
hooks:
- id: check-no-force-push
stages: [pre-push]
Expand Down Expand Up @@ -273,9 +273,11 @@ A check that had nothing to judge reports a **skip**, not a pass. The common
case is a merge subject: `Merge branch 'x'` is git's writing, so
[CC002](rules.md#cc002), [CC003](rules.md#cc003), [CC004](rules.md#cc004) and
[CC005](rules.md#cc005) decline it rather than grade prose the author never
wrote. Only git's literal `Merge ` prefix qualifies (plus `fixup! ` for
CC003); a subject that merely starts with the lowercase word is judged like
any other.
wrote. Only subjects that merge tools write qualify: git's `Merge branch
'…'`, `Merge tag '…'` and their relatives, GitLab's `Merge branch 'x' into
'y'`, and GitHub's and Bitbucket's `Merge pull request #…` (plus `fixup! ` for
CC003). A subject someone typed that starts with `Merge ` is judged like any
other.

Text mode names every skipped check in one line on stderr, leaving stdout and
the exit code untouched — a skip is still not a failure:
Expand Down
9 changes: 9 additions & 0 deletions docs/guides/organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ inherit_from = "github:my-org/.github@v1:cchk.toml"
```

Useful in a monorepo, where the shared config is already checked out.
A relative path is looked up next to the config file that names it first,
so a path written relative to that file works from any directory. Only
when nothing is there is it tried from the current directory, then from
the top of the repository.

=== "HTTPS URL"

Expand All @@ -90,6 +94,11 @@ inherit_from = "github:my-org/.github@v1:cchk.toml"

Plain HTTP is rejected.

Whatever the source, a parent config is read up to 1 MiB. A larger one, or a
local path that is not a regular file, is treated as unreachable. Only one
level is followed: an `inherit_from` inside the parent config is reported on
stderr and ignored.

!!! warning "Inheritance fails open"

If the parent config is unreachable — a network blip, a renamed file, a
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ 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.
when its message contains no recognized AI signature.

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

Expand Down
15 changes: 9 additions & 6 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.19.0
rev: v2.19.1
hooks:
- id: check-message
- id: check-branch
Expand Down Expand Up @@ -54,9 +54,12 @@ $ pre-commit install --hook-type pre-push
a branch push, such as `git push --follow-tags`, the tags on every commit the
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`.
pre-commit works out which commits a push carries from your remote-tracking
refs, such as `origin/main`, and runs no hook for a push that creates a ref
without carrying new commits by that measure. So a new tag pushed on its own,
when the commit it points at is already in a remote-tracking ref, is never seen
by `check-tag`. The same holds for a tag that `git push --follow-tags` sends
along with a branch push in that case.

## Configuring without a TOML file

Expand All @@ -65,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.0
rev: v2.19.1
hooks:
- id: check-message
args:
Expand All @@ -87,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.0
rev: v2.19.1
hooks:
- id: check-message
args: [--fix]
Expand Down
42 changes: 34 additions & 8 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ Checks that the commit message subject follows the
[Conventional Commits](https://www.conventionalcommits.org) specification:
`<type>(<scope>)!: <description>`.

A subject that a merge tool writes, such as git's `Merge branch 'main' into
topic` or GitHub's `Merge pull request #12 from …`, is accepted as it is;
whether merge commits are allowed at all is [CC006](#cc006)'s call. A subject
someone typed that merely starts with `Merge `, such as `Merge the parser
rewrite`, is judged like any other.

**Why is this bad?**

A free-form subject can only be read by a human. A structured one can be read by
Expand Down Expand Up @@ -194,6 +200,11 @@ fix(parser): handle empty input

Checks that the description in the subject line starts with a capital letter.

The description is what follows a `<type>(<scope>)!:` prefix, and the prefix
is skipped only when Conventional Commits is on and the type is one of
`allow_commit_types`. Otherwise the subject is read from its first word, so
`Added: support for X` is judged on `Added`.

**Why is this bad?**

Nothing is inherently wrong with either casing — but mixing them is. A history
Expand Down Expand Up @@ -228,6 +239,11 @@ fix: Handle empty input
Checks that the first word of the description is in the imperative mood —
`fix`, not `fixed`, `fixes`, or `fixing`.

The description is what follows a `<type>(<scope>)!:` prefix, and the prefix
is skipped only when Conventional Commits is on and the type is one of
`allow_commit_types`. Otherwise the subject is read from its first word, so
`Added: support for X` is judged on `Added`.

**Why is this bad?**

This is Git's own convention: a subject should complete the sentence *"If
Expand Down Expand Up @@ -411,7 +427,8 @@ once it is pushed, adding a message means rewriting history.

**What it does**

Rejects `fixup!` and `squash!` commits.
Rejects `fixup!`, `squash!` and `amend!` commits, the ones `git commit
--fixup`, `--squash` and `--fixup=amend:` write.

**Why is this bad?**

Expand Down Expand Up @@ -440,7 +457,9 @@ git rebase -i --autosquash main

**What it does**

Rejects work-in-progress commits — subjects beginning with `WIP`.
Rejects work-in-progress commits — subjects beginning with the word `WIP` or
with `[WIP]`, in any case and whatever follows. Words that merely start with
the letters, such as `wipe`, pass, and so does a body that mentions WIP.

**Why is this bad?**

Expand Down Expand Up @@ -471,6 +490,11 @@ git commit --amend -m "feat: add a caching layer for role lookups"

Requires a non-empty body after the subject line.

A message file is judged as the message git will store: comment lines, and
everything from the `git commit -v` scissors line on, do not count. So a
subject-only commit written in the editor fails, although git's comment block
follows the subject when the hook runs.

**Why is this bad?**

The subject says *what* changed; the diff already says that too. The body says
Expand Down Expand Up @@ -501,7 +525,9 @@ incident on 2026-05-11, long after the upstream had recovered.

**What it does**

Requires a `Signed-off-by:` trailer in the commit message.
Requires a `Signed-off-by:` trailer in the commit message: a line of its own
that names a person and an `<address>`, as `git commit --signoff` writes it.
The same text inside the subject or a sentence of the body does not count.

**Why is this bad?**

Expand Down Expand Up @@ -761,7 +787,7 @@ Checks that the current branch name follows the
[Conventional Branch](https://conventionalbranch.org/) specification:
`<type>/<description>`.

`master`, `main`, `HEAD`, and `PR-*` are always accepted.
`main`, `master`, `develop`, `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
Expand Down Expand Up @@ -804,18 +830,18 @@ feature/role-caching
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"]`.
`["staging", "production"]`.
* `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`:
To check branches exactly as the specification does, set the spec's types and
the description grammar. Its trunk branches, `main`, `master` and `develop`,
are accepted already:

```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
Expand Down