🔧 Forbid Co-authored-by trailer in commits

The Co-authored-by trailer is now forbidden. The
commit checker fails any message holding it, whatever
the case, and the docs record the ban.

AI-assisted-by: muse-spark-1.3-contributor
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrey Antukh 2026-10-14 12:06:17 +00:00
parent 1f96abd577
commit 28e735119b
5 changed files with 57 additions and 2 deletions

View File

@ -61,5 +61,6 @@ subject/body limits, and the `AI-assisted-by` trailer. Follow it exactly.
- Do not pass `--author`. Author identity comes from the local git config.
- Do not amend a commit you did not create in this session, unless explicitly asked.
- Do not bypass pre-commit hooks (`--no-verify`) unless explicitly asked.
- Never add a `Co-authored-by:` trailer. It is forbidden and fails `./scripts/check-commit`.
- Do not add untracked files that were not created in this session.
- Do not skip the `scripts/check-commit` verification step (Iron Rule 4).

View File

@ -37,8 +37,9 @@ that breaks them is wrong, even if the code is right.
- For another commit: `./scripts/check-commit -c <ref>`
- **NEVER** hand-wave the body as "one long line". If a line exceeds 76,
break it at a space.
- **NEVER add a `Co-authored-by:` trailer.** It is forbidden; `scripts/check-commit` fails the commit when any line matches it (case-insensitive).
- Exceptions inside the body (do not wrap these): `Signed-off-by:`,
`Co-authored-by:`, `AI-assisted-by:` trailers, and lines carrying a URL.
`AI-assisted-by:` trailers, and lines carrying a URL.
**AI-assisted-by trailer rules:**
- Use only the model name, e.g. `mimo-v2.5`, `deepseek-v4-flash`

View File

@ -223,6 +223,8 @@ Commit messages must follow this format:
- Keep the subject to **70 characters** or fewer
- **Wrap body lines at 76 characters or fewer** (trailers and URLs excepted)
- Separate the subject from the body with a **blank line**
- Never add a `Co-authored-by` trailer — it is forbidden and fails
`./scripts/check-commit`
You can check a commit against these rules with `./scripts/check-commit`.

View File

@ -47,10 +47,12 @@ MERGE_PATTERN = re.compile(r"^(Merge|Revert|Reapply).+[^.]$")
MAX_BODY_LINE = 76
TRAILER_PATTERN = re.compile(
r"^(Signed-off-by|Co-authored-by|Co-developed-by|Reviewed-by|"
r"^(Signed-off-by|Co-developed-by|Reviewed-by|"
r"Acked-by|Tested-by|Reported-by|Suggested-by|AI-assisted-by):"
)
FORBIDDEN_TRAILER_PATTERN = re.compile(r"^\s*Co-authored-by\s*:", re.IGNORECASE)
URL_PATTERN = re.compile(r"https?://\S+")
# ═══════════════════════════════════════════════════════════════════════════════
@ -209,6 +211,25 @@ def check_signed_off_by(message):
return True, None
def check_no_co_authored_by(message):
"""Check that the forbidden Co-authored-by trailer is absent."""
offenders = [
(line_number, line)
for line_number, line in enumerate(message.split("\n"), start=1)
if FORBIDDEN_TRAILER_PATTERN.match(line)
]
if not offenders:
return True, None
details = "\n".join(
f" line {line_number}: {line!r}"
for line_number, line in offenders
)
return False, (
"The 'Co-authored-by:' trailer is forbidden and must not appear "
"in the commit message. Offending line(s):\n" + details
)
# ═══════════════════════════════════════════════════════════════════════════════
def main():
@ -234,6 +255,7 @@ def main():
("Subject capitalized", check_subject_capitalized),
("Blank line after subject", check_body_blank_line),
("Body wrapped at 76 chars", check_body_line_length),
("No Co-authored-by trailer", check_no_co_authored_by),
]
all_ok = True

View File

@ -106,6 +106,35 @@ class BodyLineLengthTests(unittest.TestCase):
self.assertIn("line 4", error)
class ForbiddenTrailerTests(unittest.TestCase):
def assert_ok(self, message):
ok, error = check_commit.check_no_co_authored_by(message)
self.assertTrue(ok, error)
self.assertIsNone(error)
def assert_fail(self, message):
ok, error = check_commit.check_no_co_authored_by(message)
self.assertFalse(ok)
self.assertIsNotNone(error)
return error
def test_body_without_trailer_passes(self):
self.assert_ok(":bug: Fix crash\n\nBody.\n\nSigned-off-by: Someone <someone@example.com>\n")
def test_co_authored_by_fails(self):
error = self.assert_fail(":bug: Fix crash\n\nBody.\n\nCo-authored-by: Someone <someone@example.com>\n")
self.assertIn("Co-authored-by", error)
def test_co_authored_by_case_insensitive_fails(self):
self.assert_fail(":bug: Fix crash\n\nBody.\n\nco-authored-by: Someone <someone@example.com>\n")
def test_co_authored_by_in_body_fails(self):
self.assert_fail(":bug: Fix crash\n\nCo-authored-by: Someone <someone@example.com>\nBody.\n")
def test_similar_trailer_passes(self):
self.assert_ok(":bug: Fix crash\n\nBody.\n\nCo-developed-by: Someone <someone@example.com>\n")
class SubjectRulesRegressionTests(unittest.TestCase):
"""Guard the pre-existing validators against accidental breakage."""