From 9e26b4f4e52d6d4292919c2e8da9c0f12816030b Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Mon, 6 Jul 2026 16:58:01 +0300 Subject: [PATCH 1/5] docs: update changelog with releases from v2.7.0 to v2.11.0 (#458) --- docs/changelog.rst | 123 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 398bae5c..89d87726 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,123 @@ All **notable changes** to this project will be documented in this file. Full changelog available at `GitHub releases `_. -v2.7.0 (unreleased) +v2.11.0 (2026-07-06) +------------------- + +New Features +~~~~~~~~~~~~ + +* **AI attribution governance** — Added support for forbidding known AI tool + signatures (e.g., ``Co-authored-by: Copilot``) in commit messages. New + ``[commit]`` config option ``forbid_ai_attribution`` (boolean, default + ``false``) rejects commits co-authored by AI coding agents. See PR :pr:`456`. + +Bug Fixes +~~~~~~~~~ + +* Fixed ``MergeBaseValidator`` branch detection — replaced ``git branch -a`` + regex matching with ``git rev-parse --verify`` to avoid false positives + (e.g., pattern ``main`` matching ``main-staging``). See PR :pr:`451`. + +Chores +~~~~~~ + +* Added OpenSSF Scorecard workflow, badge, and pinned dependency SHAs for CI +* Migrated PyPI publishing to ``pypa/gh-action-pypi-publish`` +* Removed OpenSSF Scorecard badge after evaluation (moved to Scorecard dashboard) + + +v2.10.1 (2026-06-30) +------------------- + +Bug Fixes +~~~~~~~~~ + +* **WIP detection case-insensitivity** — ``WIP`` (``[WIP]``, ``WIP:``, ``wip:``, + etc.) is now recognized regardless of case across all common patterns. + See PR :pr:`448`. +* **Conventional commit special characters** — Allowed special characters + (parentheses, brackets, etc.) in the description part of conventional commit + messages. See PR :pr:`447`. + +Refactors +~~~~~~~~~ + +* Extracted ``_get_commit_message`` to ``BaseValidator`` to remove code + duplication across validators. See PR :pr:`445`. +* Removed legacy YAML config parsing code from ``util.py``. + See PR :pr:`444`. + + +v2.10.0 (2026-06-26) +------------------- + +New Features +~~~~~~~~~~~~ + +* **Dependabot / Renovate as default branch type** — ``dependabot/`` and + ``renovate/`` branch prefixes are now included in ``DEFAULT_BRANCH_TYPES``, + so dependency update branches are automatically recognized. + See PR :pr:`442`. + + +v2.9.0 (2026-06-22) +------------------- + +New Features +~~~~~~~~~~~~ + +* **AI agent branch prefixes (Conventional Branch v1.1.0)** — Added + ``ai/``, ``claude/``, ``codex/``, ``copilot/``, and ``cursor/`` to + ``DEFAULT_BRANCH_TYPES`` so branches created by AI coding agents are + recognized as valid. See PR :pr:`438`. + + +v2.8.1 (2026-06-22) +------------------- + +Chores +~~~~~~ + +* Fixed 27 SonarQube code-quality issues across source and test files, + including path traversal vulnerability fix, cognitive complexity + reduction, and duplicate branch consolidation. See PR :pr:`436`. +* Added SchemaStore IDE autocompletion support for ``cchk.toml``. + See PR :pr:`433`. + + +v2.8.0 (2026-06-13) +------------------- + +New Features +~~~~~~~~~~~~ + +* **Custom commit message pattern** — New ``message_pattern`` option in the + ``[commit]`` config section allows replacing the built-in Conventional Commits + regex with a user-defined regex pattern. Also supported via the + ``CCHK_MESSAGE_PATTERN`` environment variable. See PR :pr:`427`. + +Breaking Changes +~~~~~~~~~~~~~~~~ + +* **Dropped Python 3.9 support** — Minimum required Python version is now + 3.10. Type annotations have been modernized (PEP 604/585) and the + ``py.typed`` marker added for downstream type checkers. + See PR :pr:`424`. + + +v2.7.1 (2026-06-08) +------------------- + +Chores +~~~~~~ + +* Added ``auto`` to the list of imperative verbs. See PR :pr:`417`. +* Added commit-check vs GitHub Rulesets comparison table to the README. + See PR :pr:`419`. + + +v2.7.0 (2026-05-16) ------------------- New Features @@ -27,6 +143,11 @@ New Features would require force, using ``git ls-remote`` and optional ``git fetch`` to resolve the remote commit. +* **Expanded imperative verbs** — Added 156 new imperative verbs across 10 + categories (auth/security, data ops, lifecycle, I/O, debugging, UI/UX, + engineering, general), growing the total from 234 to 390. + See PR :pr:`414`. + v2.6.0 (2026-04-20) ------------------- From d10f1f956edca65696d437c37de89e59a4014d4a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:05:35 +0300 Subject: [PATCH 2/5] ci: pre-commit autoupdate (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.19 → v0.15.20](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.19...v0.15.20) - [github.com/commit-check/commit-check: v2.9.0 → v2.11.0](https://github.com/commit-check/commit-check/compare/v2.9.0...v2.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c310b62..efb135f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: trailing-whitespace - id: name-tests-test - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.19 + rev: v0.15.20 hooks: - id: ruff-check args: [ --fix ] @@ -33,7 +33,7 @@ repos: hooks: - id: codespell - repo: https://github.com/commit-check/commit-check - rev: v2.9.0 + rev: v2.11.0 hooks: - id: check-message stages: [commit-msg] From 092a82426c5cbecffb400cd30d18c034610a3d4a Mon Sep 17 00:00:00 2001 From: Randolph Sapp Date: Thu, 9 Jul 2026 01:27:38 -0500 Subject: [PATCH 3/5] fix(require_signed_off_by): allow any name (#462) This regex is too strict right now. Same could probably be said about the author_name regex. It catches bots like dependabot incorrectly. Signed-off-by: Randolph Sapp --- commit_check/rules_catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commit_check/rules_catalog.py b/commit_check/rules_catalog.py index 108b4134..10da2e88 100644 --- a/commit_check/rules_catalog.py +++ b/commit_check/rules_catalog.py @@ -100,7 +100,7 @@ class RuleCatalogEntry: ), RuleCatalogEntry( check="require_signed_off_by", - regex=r"Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+>", + regex=r"Signed-off-by: .+ <.+@.+>", error="Signed-off-by not found in latest commit", suggest="git commit --amend --signoff or use --signoff on commit", ), From df32a83c50577198615d6d0df20d8537a2c3a3bc Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Thu, 9 Jul 2026 09:49:24 +0300 Subject: [PATCH 4/5] test: cover default signoff regex for bot and regular names (#463) * test: cover default signoff regex for bot and regular names The existing SignoffValidator tests pass an inline regex, so the default pattern in rules_catalog was never exercised. Build the rule via RuleBuilder and assert the shipped default: - accepts a bracketed bot name (Signed-off-by: dependabot[bot] <...>) - accepts a regular Name signoff - rejects a message with no signoff trailer * ci: auto fixes from pre-commit.com hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- tests/engine_test.py | 48 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/engine_test.py b/tests/engine_test.py index 7cea27dd..885a39f9 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -23,7 +23,7 @@ ForcePushValidator, AiAttributionValidator, ) -from commit_check.rule_builder import ValidationRule +from commit_check.rule_builder import ValidationRule, RuleBuilder # String constants used across tests (defined once to avoid duplication) GIT_CONFIG_VALUE = "commit_check.engine.get_git_config_value" @@ -648,6 +648,18 @@ def test_subject_length_validator_min_too_short(self): class TestSignoffValidator: + @staticmethod + def _default_signoff_rule(): + """Build the require_signed_off_by rule from the default catalog regex. + + Unlike the tests that pass an inline regex, this exercises the actual + default pattern shipped in rules_catalog, so a regression in that + pattern is caught here. + """ + builder = RuleBuilder({"commit": {"require_signed_off_by": True}}) + rules = builder.build_all_rules() + return next(r for r in rules if r.check == "require_signed_off_by") + @pytest.mark.benchmark def test_signoff_validator_valid(self): """Test SignoffValidator with valid signoff.""" @@ -662,6 +674,40 @@ def test_signoff_validator_valid(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark + def test_default_signoff_accepts_bot_name(self): + """Default regex accepts a bracketed bot name such as dependabot[bot].""" + validator = SignoffValidator(self._default_signoff_rule()) + context = ValidationContext( + stdin_text=( + "chore: bump dep\n\nSigned-off-by: dependabot[bot] " + ) + ) + + result = validator.validate(context) + assert result == ValidationResult.PASS + + @pytest.mark.benchmark + def test_default_signoff_accepts_regular_name(self): + """Default regex accepts a regular name and email signoff.""" + validator = SignoffValidator(self._default_signoff_rule()) + context = ValidationContext( + stdin_text="feat: add feature\n\nSigned-off-by: John Doe " + ) + + result = validator.validate(context) + assert result == ValidationResult.PASS + + @pytest.mark.benchmark + def test_default_signoff_rejects_missing_signoff(self): + """Default regex rejects a message without any signoff trailer.""" + validator = SignoffValidator(self._default_signoff_rule()) + context = ValidationContext(stdin_text="feat: add feature") + + with patch("commit_check.util._print_failure"): + result = validator.validate(context) + assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_signoff_validator_missing_signoff(self): """Test SignoffValidator with missing signoff.""" From 47bea4528ff6da056b8c6630d765d06b9c25e338 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Thu, 9 Jul 2026 10:15:02 +0300 Subject: [PATCH 5/5] fix: skip signoff check for ignored authors (#464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SignoffValidator used the base skip helper, so it was the only commit-level check that did not honor ignore_authors — a bot in the ignore list still had its signoff validated. Use _should_skip_commit_validation so signoff is skipped for ignored authors, consistent with the message, author, and body checks. Add a test covering the skip behavior. --- commit_check/engine.py | 2 +- tests/engine_test.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/commit_check/engine.py b/commit_check/engine.py index edb42278..ff06ad40 100644 --- a/commit_check/engine.py +++ b/commit_check/engine.py @@ -493,7 +493,7 @@ class SignoffValidator(BaseValidator): """Validates that commit messages contain required signoff trailer.""" def validate(self, context: ValidationContext) -> ValidationResult: - if self._should_skip_validation(context): + if self._should_skip_commit_validation(context): return ValidationResult.PASS message = self._get_commit_message(context) diff --git a/tests/engine_test.py b/tests/engine_test.py index 885a39f9..73abc644 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -708,6 +708,23 @@ def test_default_signoff_rejects_missing_signoff(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark + def test_default_signoff_skips_ignored_author(self, mock_get_commit_info): + """Signoff check is skipped when the author is in ignore_authors. + + A commit with no signoff would normally fail, but an ignored author + (e.g. a bot) should bypass the signoff check just like every other + commit check. + """ + mock_get_commit_info.return_value = "dependabot[bot]" + validator = SignoffValidator(self._default_signoff_rule()) + config = {"commit": {"ignore_authors": ["dependabot[bot]"]}} + context = ValidationContext(stdin_text="chore: bump dep", config=config) + + result = validator.validate(context) + assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_signoff_validator_missing_signoff(self): """Test SignoffValidator with missing signoff."""