From 45b7c18775a199d8fcc5820355d7cbc1d0f8faa5 Mon Sep 17 00:00:00 2001 From: Klaus Zerwes Date: Mon, 6 Jul 2026 18:52:42 +0200 Subject: [PATCH 1/6] feature: make the author check patterns configurable --- .gitignore | 1 + README.rst | 11 ++++++++--- commit_check/config_merger.py | 6 ++++++ commit_check/main.py | 14 ++++++++++++++ commit_check/rule_builder.py | 19 +++++++++++++++++++ docs/configuration.rst | 16 ++++++++++++++++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6e9ea54f..3dfd5ade 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ commit_check.egg-info __pycache__ .mypy_cache .vscode +*.swp venv .venv UNKNOWN.egg-info diff --git a/README.rst b/README.rst index 8952eb53..77cb82c2 100644 --- a/README.rst +++ b/README.rst @@ -75,7 +75,7 @@ Quick Start repos: - repo: https://github.com/commit-check/commit-check - rev: v2.6.0 + rev: v2.10.2 hooks: - id: check-message - id: check-branch @@ -198,12 +198,17 @@ For one-off checks or CI/CD pipelines, you can configure via CLI arguments or en # In pre-commit hooks (.pre-commit-config.yaml) repos: - repo: https://github.com/commit-check/commit-check - rev: v2.6.0 + rev: v2.10.2 hooks: - id: check-message args: - --subject-imperative=false - --subject-max-length=100 + - id: check-author-name + args: + - --no-banner + - --author-name + - --author-name-pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$ See the `Configuration documentation `_ for all available options. @@ -224,7 +229,7 @@ branch's configured upstream: # In pre-commit hooks (.pre-commit-config.yaml) repos: - repo: https://github.com/commit-check/commit-check - rev: v2.6.0 + rev: v2.10.2 hooks: - id: check-no-force-push stages: [pre-push] diff --git a/commit_check/config_merger.py b/commit_check/config_merger.py index 149e9da0..c9c88e56 100644 --- a/commit_check/config_merger.py +++ b/commit_check/config_merger.py @@ -78,6 +78,8 @@ def get_default_config() -> dict[str, Any]: "require_signed_off_by": DEFAULT_BOOLEAN_RULES["require_signed_off_by"], "ignore_authors": [], "ai_attribution": DEFAULT_AI_ATTRIBUTION, + "author_email_pattern": "^.+@.+$", + "author_name_pattern": "", }, "branch": { "conventional_branch": True, @@ -123,6 +125,8 @@ class ConfigMerger: "CCHK_REQUIRE_SIGNED_OFF_BY": ("commit", "require_signed_off_by", parse_bool), "CCHK_IGNORE_AUTHORS": ("commit", "ignore_authors", parse_list), "CCHK_AI_ATTRIBUTION": ("commit", "ai_attribution", str), + "CCHK_AUTHOR_EMAIL_PATTERN": ("commit", "author_email_pattern", str), + "CCHK_AUTHOR_NAME_PATTERN": ("commit", "author_name_pattern", str), # Branch section "CCHK_CONVENTIONAL_BRANCH": ("branch", "conventional_branch", parse_bool), "CCHK_ALLOW_BRANCH_TYPES": ("branch", "allow_branch_types", parse_list), @@ -151,6 +155,8 @@ class ConfigMerger: "require_signed_off_by": ("commit", "require_signed_off_by"), "ignore_authors": ("commit", "ignore_authors"), "ai_attribution": ("commit", "ai_attribution"), + "author_email_pattern": ("commit", "author_email_pattern"), + "author_name_pattern": ("commit", "author_name_pattern"), # Branch section "conventional_branch": ("branch", "conventional_branch"), "allow_branch_types": ("branch", "allow_branch_types"), diff --git a/commit_check/main.py b/commit_check/main.py index c5c814eb..91762d90 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -303,6 +303,20 @@ def _get_parser() -> argparse.ArgumentParser: "'forbid' rejects commits with known AI tool signatures.", ) + commit_group.add_argument( + "--author-email-pattern", + type=str, + default=None, + help="regex to check author email", + ) + + commit_group.add_argument( + "--author-name-pattern", + type=str, + default=None, + help="regex to check author name", + ) + # Branch configuration options branch_group = parser.add_argument_group( "branch options", "Configuration options for --branch validation" diff --git a/commit_check/rule_builder.py b/commit_check/rule_builder.py index cc768a00..a406780a 100644 --- a/commit_check/rule_builder.py +++ b/commit_check/rule_builder.py @@ -141,6 +141,12 @@ def _build_single_rule( return self._build_author_list_rule(catalog_entry, "ignore_authors") elif check == "ai_attribution": return self._build_ai_attribution_rule(catalog_entry) + elif check == "author_email": + return self._build_author_pattern_rule( + catalog_entry, "author_email_pattern" + ) + elif check == "author_name": + return self._build_author_pattern_rule(catalog_entry, "author_name_pattern") elif check == "merge_base": return self._build_merge_base_rule(catalog_entry) else: @@ -236,6 +242,19 @@ def _build_author_list_rule( return ValidationRule(check=catalog_entry.check, ignored=author_list) return None + def _build_author_pattern_rule( + self, catalog_entry: RuleCatalogEntry, config_key: str + ) -> ValidationRule | None: + """Build author name or email validation rule.""" + regex = self.commit_config.get(config_key, "").strip() + + return ValidationRule( + check=catalog_entry.check, + regex=regex, + error=catalog_entry.error, + suggest=catalog_entry.suggest, + ) + def _build_merge_base_rule( self, catalog_entry: RuleCatalogEntry ) -> ValidationRule | None: diff --git a/docs/configuration.rst b/docs/configuration.rst index 1a4416d2..a4e4d776 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -274,6 +274,12 @@ Configuration can also be set via environment variables with the ``CCHK_`` prefi * - ``ignore_authors = ["bot"]`` - ``CCHK_IGNORE_AUTHORS=bot,user`` - ``--ignore-authors=bot,user`` + * - ``author_email_pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` + - ``CCHK_AUTHOR_EMAIL_PATTERN=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` + - ```--author-email-pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` + * - ``author_name_pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` + - ``CCHK_AUTHOR_NAME_PATTERN=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` + - ``--author-name-pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` * - ``conventional_branch = true`` - ``CCHK_CONVENTIONAL_BRANCH=true`` - ``--conventional-branch=true`` @@ -397,6 +403,16 @@ Options Table Description - 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]"``). + * - commit + - author_email_pattern + - str + - ^.+@.+$ + - Custom regex pattern for author email check + * - commit + - author_name_pattern + - str + - "" (disabled) + - Custom regex pattern for author name check * - commit - require_signed_off_by - bool From c53bc9245c1da05f95c5e198e2869b78a0f59e08 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Mon, 6 Jul 2026 21:10:45 +0300 Subject: [PATCH 2/6] chore: update pre-commit rev to latest version Co-authored-by: Xianpeng Shen --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 77cb82c2..3d9c813c 100644 --- a/README.rst +++ b/README.rst @@ -75,7 +75,7 @@ Quick Start repos: - repo: https://github.com/commit-check/commit-check - rev: v2.10.2 + rev: v2.11.0 hooks: - id: check-message - id: check-branch @@ -198,7 +198,7 @@ For one-off checks or CI/CD pipelines, you can configure via CLI arguments or en # In pre-commit hooks (.pre-commit-config.yaml) repos: - repo: https://github.com/commit-check/commit-check - rev: v2.10.2 + rev: v2.11.0 hooks: - id: check-message args: @@ -229,7 +229,7 @@ branch's configured upstream: # In pre-commit hooks (.pre-commit-config.yaml) repos: - repo: https://github.com/commit-check/commit-check - rev: v2.10.2 + rev: v2.11.0 hooks: - id: check-no-force-push stages: [pre-push] From 9b3cfefa3006cc3a8040d9afbc16ce9c0f5f7f6d Mon Sep 17 00:00:00 2001 From: Klaus Zerwes Date: Mon, 6 Jul 2026 22:25:32 +0200 Subject: [PATCH 3/6] fall back to catalog_entry.regex in build_author_pattern_rule --- commit_check/rule_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commit_check/rule_builder.py b/commit_check/rule_builder.py index a406780a..6e7f7b13 100644 --- a/commit_check/rule_builder.py +++ b/commit_check/rule_builder.py @@ -246,7 +246,9 @@ def _build_author_pattern_rule( self, catalog_entry: RuleCatalogEntry, config_key: str ) -> ValidationRule | None: """Build author name or email validation rule.""" - regex = self.commit_config.get(config_key, "").strip() + regex = catalog_entry.regex + if self.commit_config.get(config_key, ""): + regex = self.commit_config.get(config_key, "").strip() return ValidationRule( check=catalog_entry.check, From 500148b0983e68d219bc7dcea7bd01b63101448c Mon Sep 17 00:00:00 2001 From: Klaus Zerwes Date: Mon, 6 Jul 2026 22:37:13 +0200 Subject: [PATCH 4/6] fix: removed extra backtick --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index a4e4d776..9f057f46 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -276,7 +276,7 @@ Configuration can also be set via environment variables with the ``CCHK_`` prefi - ``--ignore-authors=bot,user`` * - ``author_email_pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` - ``CCHK_AUTHOR_EMAIL_PATTERN=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` - - ```--author-email-pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` + - ``--author-email-pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` * - ``author_name_pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` - ``CCHK_AUTHOR_NAME_PATTERN=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` - ``--author-name-pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` From 8113c6c045886427f13d07636d8c3aa9ba9a842e Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Thu, 23 Jul 2026 18:20:09 +0000 Subject: [PATCH 5/6] test: use inclusive doc examples; cover configurable author patterns Add tests for author_name_pattern / author_email_pattern: custom name and email (company-domain) patterns pass/fail, env-var mapping, and a regression guard that an empty pattern falls back to the built-in regex instead of disabling the check. Replace the Western-name-only example regex in the docs and README with an email-domain example and a permissive full-name example, and correct the author_name_pattern description (empty means built-in default, not disabled). --- README.md | 5 ++++ docs/configuration.rst | 18 ++++++------- tests/config_merger_test.py | 7 +++++ tests/engine_test.py | 51 +++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 75a90176..adbfece3 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,11 @@ repos: args: - --subject-imperative=false - --subject-max-length=100 + - id: check-author-email + args: + - --no-banner + - --author-email + - --author-email-pattern=^.+@example\.com$ ``` See the [Configuration documentation](https://commit-check.github.io/commit-check/configuration.html) for all available options. diff --git a/docs/configuration.rst b/docs/configuration.rst index 9f057f46..cb265851 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -274,12 +274,12 @@ Configuration can also be set via environment variables with the ``CCHK_`` prefi * - ``ignore_authors = ["bot"]`` - ``CCHK_IGNORE_AUTHORS=bot,user`` - ``--ignore-authors=bot,user`` - * - ``author_email_pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` - - ``CCHK_AUTHOR_EMAIL_PATTERN=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` - - ``--author-email-pattern=^[-a-zA-Z0-9]*\.[-a-zA-Z0-9]*@.*$`` - * - ``author_name_pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` - - ``CCHK_AUTHOR_NAME_PATTERN=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` - - ``--author-name-pattern=^[A-Z]+[-a-zA-Z0-9]+ [A-Z]+[-a-zA-Z0-9]+$`` + * - ``author_email_pattern=^.+@example\.com$`` + - ``CCHK_AUTHOR_EMAIL_PATTERN=^.+@example\.com$`` + - ``--author-email-pattern=^.+@example\.com$`` + * - ``author_name_pattern=^.+ .+$`` + - ``CCHK_AUTHOR_NAME_PATTERN=^.+ .+$`` + - ``--author-name-pattern=^.+ .+$`` * - ``conventional_branch = true`` - ``CCHK_CONVENTIONAL_BRANCH=true`` - ``--conventional-branch=true`` @@ -407,12 +407,12 @@ Options Table Description - author_email_pattern - str - ^.+@.+$ - - Custom regex pattern for author email check + - Custom regex for the author email check. When empty, the built-in default pattern is used. * - commit - author_name_pattern - str - - "" (disabled) - - Custom regex pattern for author name check + - "" (built-in default) + - Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). * - commit - require_signed_off_by - bool diff --git a/tests/config_merger_test.py b/tests/config_merger_test.py index fa41060f..eddf0e9e 100644 --- a/tests/config_merger_test.py +++ b/tests/config_merger_test.py @@ -175,6 +175,13 @@ def test_parse_branch_env_vars(self, monkeypatch): assert config["branch"]["conventional_branch"] is False assert config["branch"]["allow_branch_types"] == ["feature", "bugfix"] + def test_parse_author_pattern_env_vars(self, monkeypatch): + monkeypatch.setenv("CCHK_AUTHOR_NAME_PATTERN", r"^[A-Z][a-z]+ [A-Z][a-z]+$") + monkeypatch.setenv("CCHK_AUTHOR_EMAIL_PATTERN", r"^.+@company\.com$") + config = ConfigMerger.parse_env_vars() + assert config["commit"]["author_name_pattern"] == r"^[A-Z][a-z]+ [A-Z][a-z]+$" + assert config["commit"]["author_email_pattern"] == r"^.+@company\.com$" + def test_invalid_env_var_is_skipped(self, monkeypatch, capsys): monkeypatch.setenv("CCHK_SUBJECT_MAX_LENGTH", "invalid") config = ConfigMerger.parse_env_vars() diff --git a/tests/engine_test.py b/tests/engine_test.py index ec4c4e64..48987845 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -502,6 +502,57 @@ def test_author_validator_ignored_author( result = validator.validate(context) assert result == ValidationResult.PASS + +class TestAuthorPatternConfig: + """Tests for configurable author_name_pattern / author_email_pattern. + + Rules are built through RuleBuilder so the actual config resolution + (custom pattern override + fallback to the built-in catalog regex) is + exercised, not just an inline regex. + """ + + @staticmethod + def _author_rule(commit_config, check): + builder = RuleBuilder({"commit": commit_config}) + rules = builder.build_all_rules() + return next(r for r in rules if r.check == check) + + @staticmethod + def _validate(rule, author_value): + validator = AuthorValidator(rule) + with patch("commit_check.util._print_failure"): + return validator.validate(ValidationContext(stdin_text=author_value)) + + @pytest.mark.benchmark + def test_custom_name_pattern_pass_and_fail(self): + """A custom author_name_pattern accepts matches and rejects non-matches.""" + rule = self._author_rule( + {"author_name_pattern": r"^[A-Z][a-z]+ [A-Z][a-z]+$"}, "author_name" + ) + assert self._validate(rule, "Jane Doe") == ValidationResult.PASS + assert self._validate(rule, "jane") == ValidationResult.FAIL + + @pytest.mark.benchmark + def test_custom_email_pattern_enforces_domain(self): + """A custom author_email_pattern can enforce a company domain.""" + rule = self._author_rule( + {"author_email_pattern": r"^.+@company\.com$"}, "author_email" + ) + assert self._validate(rule, "bob@company.com") == ValidationResult.PASS + assert self._validate(rule, "bob@gmail.com") == ValidationResult.FAIL + + @pytest.mark.benchmark + def test_default_name_pattern_uses_builtin_regex(self): + """With no custom pattern, the built-in catalog regex still applies. + + Regression guard: an empty/omitted author_name_pattern must not disable + the check — it should fall back to the shipped default so an invalid + name is still rejected. + """ + rule = self._author_rule({}, "author_name") + assert self._validate(rule, "Jane Doe") == ValidationResult.PASS + assert self._validate(rule, "12345 !!!") == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_author_with_allowed_list(self): """Test author validation with allowed list.""" From 0e67f517a287bc18cd7dab12ad7d71714be7d63a Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 24 Jul 2026 05:13:39 +0300 Subject: [PATCH 6/6] fix: address PR review feedback - Move 4 swallowed test methods back to TestAuthorValidator class - Add metavar='REGEX' to --author-email-pattern and --author-name-pattern - Clarify help text: --author-email/name-pattern requires the check enabled --- commit_check/main.py | 6 ++- docs/configuration.rst | 2 + tests/engine_test.py | 102 ++++++++++++++++++++--------------------- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/commit_check/main.py b/commit_check/main.py index ce96f824..af7d2cd6 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -315,14 +315,16 @@ def _get_parser() -> argparse.ArgumentParser: "--author-email-pattern", type=str, default=None, - help="regex to check author email", + metavar="REGEX", + help="regex to check author email (requires --author-email)", ) commit_group.add_argument( "--author-name-pattern", type=str, default=None, - help="regex to check author name", + metavar="REGEX", + help="regex to check author name (requires --author-name)", ) # Branch configuration options diff --git a/docs/configuration.rst b/docs/configuration.rst index cb265851..6b42c9b9 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -408,11 +408,13 @@ Options Table Description - str - ^.+@.+$ - Custom regex for the author email check. When empty, the built-in default pattern is used. + This option only takes effect when the author_email check is enabled (``-e`` / ``--author-email``). * - commit - author_name_pattern - str - "" (built-in default) - Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). + This option only takes effect when the author_name check is enabled (``-n`` / ``--author-name``). * - commit - require_signed_off_by - bool diff --git a/tests/engine_test.py b/tests/engine_test.py index 48987845..9d97ce81 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -502,57 +502,6 @@ def test_author_validator_ignored_author( result = validator.validate(context) assert result == ValidationResult.PASS - -class TestAuthorPatternConfig: - """Tests for configurable author_name_pattern / author_email_pattern. - - Rules are built through RuleBuilder so the actual config resolution - (custom pattern override + fallback to the built-in catalog regex) is - exercised, not just an inline regex. - """ - - @staticmethod - def _author_rule(commit_config, check): - builder = RuleBuilder({"commit": commit_config}) - rules = builder.build_all_rules() - return next(r for r in rules if r.check == check) - - @staticmethod - def _validate(rule, author_value): - validator = AuthorValidator(rule) - with patch("commit_check.util._print_failure"): - return validator.validate(ValidationContext(stdin_text=author_value)) - - @pytest.mark.benchmark - def test_custom_name_pattern_pass_and_fail(self): - """A custom author_name_pattern accepts matches and rejects non-matches.""" - rule = self._author_rule( - {"author_name_pattern": r"^[A-Z][a-z]+ [A-Z][a-z]+$"}, "author_name" - ) - assert self._validate(rule, "Jane Doe") == ValidationResult.PASS - assert self._validate(rule, "jane") == ValidationResult.FAIL - - @pytest.mark.benchmark - def test_custom_email_pattern_enforces_domain(self): - """A custom author_email_pattern can enforce a company domain.""" - rule = self._author_rule( - {"author_email_pattern": r"^.+@company\.com$"}, "author_email" - ) - assert self._validate(rule, "bob@company.com") == ValidationResult.PASS - assert self._validate(rule, "bob@gmail.com") == ValidationResult.FAIL - - @pytest.mark.benchmark - def test_default_name_pattern_uses_builtin_regex(self): - """With no custom pattern, the built-in catalog regex still applies. - - Regression guard: an empty/omitted author_name_pattern must not disable - the check — it should fall back to the shipped default so an invalid - name is still rejected. - """ - rule = self._author_rule({}, "author_name") - assert self._validate(rule, "Jane Doe") == ValidationResult.PASS - assert self._validate(rule, "12345 !!!") == ValidationResult.FAIL - @pytest.mark.benchmark def test_validate_author_with_allowed_list(self): """Test author validation with allowed list.""" @@ -607,6 +556,57 @@ def test_get_author_value_with_email_format(self): assert author_value == "test@example.com" +class TestAuthorPatternConfig: + """Tests for configurable author_name_pattern / author_email_pattern. + + Rules are built through RuleBuilder so the actual config resolution + (custom pattern override + fallback to the built-in catalog regex) is + exercised, not just an inline regex. + """ + + @staticmethod + def _author_rule(commit_config, check): + builder = RuleBuilder({"commit": commit_config}) + rules = builder.build_all_rules() + return next(r for r in rules if r.check == check) + + @staticmethod + def _validate(rule, author_value): + validator = AuthorValidator(rule) + with patch("commit_check.util._print_failure"): + return validator.validate(ValidationContext(stdin_text=author_value)) + + @pytest.mark.benchmark + def test_custom_name_pattern_pass_and_fail(self): + """A custom author_name_pattern accepts matches and rejects non-matches.""" + rule = self._author_rule( + {"author_name_pattern": r"^[A-Z][a-z]+ [A-Z][a-z]+$"}, "author_name" + ) + assert self._validate(rule, "Jane Doe") == ValidationResult.PASS + assert self._validate(rule, "jane") == ValidationResult.FAIL + + @pytest.mark.benchmark + def test_custom_email_pattern_enforces_domain(self): + """A custom author_email_pattern can enforce a company domain.""" + rule = self._author_rule( + {"author_email_pattern": r"^.+@company\.com$"}, "author_email" + ) + assert self._validate(rule, "bob@company.com") == ValidationResult.PASS + assert self._validate(rule, "bob@gmail.com") == ValidationResult.FAIL + + @pytest.mark.benchmark + def test_default_name_pattern_uses_builtin_regex(self): + """With no custom pattern, the built-in catalog regex still applies. + + Regression guard: an empty/omitted author_name_pattern must not disable + the check — it should fall back to the shipped default so an invalid + name is still rejected. + """ + rule = self._author_rule({}, "author_name") + assert self._validate(rule, "Jane Doe") == ValidationResult.PASS + assert self._validate(rule, "12345 !!!") == ValidationResult.FAIL + + class TestCommitTypeValidator: @pytest.mark.benchmark def test_commit_type_validator_merge_commits(self):