From 272496d80dd5a418c653cbcab15eec61956bb36c Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:02:42 +0000 Subject: [PATCH 1/2] docs: bring the site in step with v2.19.1 Pins move to v2.19.1 and the changelog gains its entry and a highlights row. The pages now describe what 2.19.1 changed: develop is a built-in branch name, only tool-written merge subjects are exempt, a message file is judged without git's comments or -v diff, CC009 covers squash! and amend!, CC010 catches every WIP marker, CC012 needs a real trailer, CC002 and CC003 skip only an allowed type prefix, the config is found from a subdirectory, and a parent config is read up to 1 MiB. --- docs/changelog.md | 74 +++++++++++++++++++++++++++++++++++++ docs/configuration.md | 15 +++++--- docs/example.md | 10 +++-- docs/guides/organization.md | 8 ++++ docs/guides/policies.md | 2 +- docs/guides/pre-commit.md | 10 +++-- docs/rules.md | 42 +++++++++++++++++---- 7 files changed, 139 insertions(+), 22 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 564ab60..e7d95f6 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.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) | @@ -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 `. + 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 diff --git a/docs/configuration.md b/docs/configuration.md index c2cd45e..7c0d983 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 @@ -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 ``` @@ -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: @@ -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"]` | `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`) | @@ -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 | 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 | 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]"`). | @@ -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 `/` 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). | diff --git a/docs/example.md b/docs/example.md index f2be3a2..5f59cf4 100644 --- a/docs/example.md +++ b/docs/example.md @@ -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] @@ -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: diff --git a/docs/guides/organization.md b/docs/guides/organization.md index 2338787..8c4b6f8 100644 --- a/docs/guides/organization.md +++ b/docs/guides/organization.md @@ -81,6 +81,9 @@ 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, + then in the current directory, then at the top of the repository, so it + resolves the same from any directory. === "HTTPS URL" @@ -90,6 +93,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 diff --git a/docs/guides/policies.md b/docs/guides/policies.md index 0aade37..f1ecc09 100644 --- a/docs/guides/policies.md +++ b/docs/guides/policies.md @@ -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" diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md index 4e95f24..7bbc510 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.0 + rev: v2.19.1 hooks: - id: check-message - id: check-branch @@ -56,7 +56,9 @@ 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`. +already on the remote, is never seen by `check-tag`. The same holds for a tag +that `git push --follow-tags` sends along with a branch push when the commit it +points at is already on the remote. ## Configuring without a TOML file @@ -65,7 +67,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: @@ -87,7 +89,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] diff --git a/docs/rules.md b/docs/rules.md index a16acac..429010d 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -160,6 +160,12 @@ Checks that the commit message subject follows the [Conventional Commits](https://www.conventionalcommits.org) specification: `()!: `. +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 @@ -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 `()!:` 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 @@ -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 `()!:` 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 @@ -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?** @@ -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?** @@ -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 @@ -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 `
`, 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?** @@ -761,7 +787,7 @@ Checks that the current branch name follows the [Conventional Branch](https://conventionalbranch.org/) specification: `/`. -`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 @@ -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 From e28f8f7fd1327fb4860e7a048853764b55d5f535 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:15:52 +0000 Subject: [PATCH 2/2] docs: tighten three statements from review Use the same branch names in every column of the allow_branch_names row, say that a relative inherit_from is the same from any directory only when it sits next to the config, and say that pre-commit judges what a push carries from the remote-tracking refs. --- docs/configuration.md | 2 +- docs/guides/organization.md | 5 +++-- docs/guides/pre-commit.md | 11 ++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7c0d983..4fe674c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -313,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 = ["staging"]` | `CCHK_ALLOW_BRANCH_NAMES=staging,production` | `--allow-branch-names=staging,production` | +| `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`) | diff --git a/docs/guides/organization.md b/docs/guides/organization.md index 8c4b6f8..6e5c488 100644 --- a/docs/guides/organization.md +++ b/docs/guides/organization.md @@ -82,8 +82,9 @@ 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, - then in the current directory, then at the top of the repository, so it - resolves the same from any directory. + 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" diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md index 7bbc510..615f389 100644 --- a/docs/guides/pre-commit.md +++ b/docs/guides/pre-commit.md @@ -54,11 +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`. The same holds for a tag -that `git push --follow-tags` sends along with a branch push when the commit it -points at is already on the remote. +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