diff --git a/docs/blog/posts/ai-commit-attribution-policy.md b/docs/blog/posts/ai-commit-attribution-policy.md index 25ba1d5..d90a86c 100644 --- a/docs/blog/posts/ai-commit-attribution-policy.md +++ b/docs/blog/posts/ai-commit-attribution-policy.md @@ -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. diff --git a/docs/changelog.md b/docs/changelog.md index 7facb1c..564ab60 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.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) | @@ -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 `/` 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 `, `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 diff --git a/docs/configuration.md b/docs/configuration.md index 8c10a40..c2cd45e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 @@ -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: @@ -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` | @@ -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 `/` 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`. | diff --git a/docs/example.md b/docs/example.md index dd582f0..f2be3a2 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.18.3 + rev: v2.19.0 hooks: - id: check-no-force-push stages: [pre-push] @@ -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" @@ -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": "" } ``` diff --git a/docs/guides/policies.md b/docs/guides/policies.md index ec18ef5..0aade37 100644 --- a/docs/guides/policies.md +++ b/docs/guides/policies.md @@ -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. + !!! warning "This checks disclosure, not authorship" These rules read commit metadata. They detect a commit that *says* it diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md index 98aca63..4e95f24 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.18.3 + rev: v2.19.0 hooks: - id: check-message - id: check-branch @@ -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 +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`. @@ -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: @@ -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] diff --git a/docs/guides/python-api.md b/docs/guides/python-api.md index 6d1d662..43191cd 100644 --- a/docs/guides/python-api.md +++ b/docs/guides/python-api.md @@ -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 "" }, ], } @@ -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. diff --git a/docs/rules.md b/docs/rules.md index 8ed465d..a16acac 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -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?** @@ -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 @@ -787,14 +793,35 @@ feature/role-caching * `branch.conventional_branch` — set to `false` to disable this rule. * `branch.allow_branch_types` — the accepted `` 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**