gitlint-rai 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitlint_rai-0.0.0/LICENSE +30 -0
- gitlint_rai-0.0.0/PKG-INFO +23 -0
- gitlint_rai-0.0.0/README.md +67 -0
- gitlint_rai-0.0.0/gitlint_rai/__init__.py +3 -0
- gitlint_rai-0.0.0/gitlint_rai/__main__.py +14 -0
- gitlint_rai-0.0.0/gitlint_rai/rules.py +80 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/PKG-INFO +23 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/SOURCES.txt +14 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/dependency_links.txt +1 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/entry_points.txt +5 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/requires.txt +1 -0
- gitlint_rai-0.0.0/gitlint_rai.egg-info/top_level.txt +1 -0
- gitlint_rai-0.0.0/pyproject.toml +96 -0
- gitlint_rai-0.0.0/setup.cfg +4 -0
- gitlint_rai-0.0.0/tests/test_integration.py +66 -0
- gitlint_rai-0.0.0/tests/test_rules.py +105 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Polyform Shield License 1.0.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ChecKMarK DevTools / Ashley Childress
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to use,
|
|
7
|
+
copy, modify, and/or distribute the Software for any purpose—including personal, professional, or commercial use—subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
1. **No Profit or Monetization.**
|
|
10
|
+
The Software may not be sold, rebranded, licensed, hosted as a paid SaaS, or used as the basis of any product or service from which you or your organization earns revenue or profit, without explicit written permission from Ashley Childress.
|
|
11
|
+
|
|
12
|
+
- **You CAN** use, copy, fork, or adapt this for your own workflows, inside your company, for client projects, demos, education, or anything else—as long as you’re not selling the code, charging for it, or making money from the project itself.
|
|
13
|
+
- **You CANNOT** resell, offer as a paid service, or monetize this project or its derivatives without prior written approval.
|
|
14
|
+
|
|
15
|
+
2. **Endorsement.**
|
|
16
|
+
Neither the name of ChecKMarK DevTools / Ashley Childress nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
3. **Attribution.**
|
|
19
|
+
Any public fork, copy, or substantial reuse must include this LICENSE and a clear statement in your documentation or README:
|
|
20
|
+
“Based on original work by ChecKMarK DevTools / Ashley Childress – see \[original repo link\].”
|
|
21
|
+
|
|
22
|
+
4. **No Warranty.**
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
24
|
+
|
|
25
|
+
Full license text: https://polyformproject.org/licenses/shield/1.0.0/
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
This project is licensed under Polyform Shield License 1.0.0.
|
|
30
|
+
For exceptions or monetization/commercialization questions, contact Ashley Childress at [human@checkmarkdevtools.dev](mailto:human@checkmarkdevtools.dev).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitlint-rai
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Gitlint plugin for RAI footer validation
|
|
5
|
+
Author-email: Ashley Childress <human@checkmarkdevtools.dev>
|
|
6
|
+
License-Expression: LicenseRef-PolyForm-Shield-1.0.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ChecKMarKDevTools/rai-lint
|
|
8
|
+
Project-URL: Repository, https://github.com/ChecKMarKDevTools/rai-lint
|
|
9
|
+
Project-URL: Issues, https://github.com/ChecKMarKDevTools/rai-lint/issues
|
|
10
|
+
Keywords: gitlint,rai,ai-attribution,commit-validation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: <3.13,>=3.11
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: gitlint>=0.19.1
|
|
23
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# gitlint-rai
|
|
2
|
+
|
|
3
|
+
Gitlint plugin for enforcing AI attribution in commit messages using Git commit message trailers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install gitlint-rai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add gitlint-rai
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Add to `.gitlint`:
|
|
20
|
+
|
|
21
|
+
```ini
|
|
22
|
+
[general]
|
|
23
|
+
extra-path=gitlint_rai
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
gitlint
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or use the standalone wrapper:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gitlint-rai
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Verify the rule loaded:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
gitlint --list-rules | grep rai-footer-exists
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Expected output:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
rai-footer-exists Commit message must include a valid RAI footer
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Valid Footer Formats
|
|
51
|
+
|
|
52
|
+
1. **`Authored-by: [Human] <email>`** - Human only, no AI involvement
|
|
53
|
+
2. **`Commit-generated-by: [AI Tool] <email>`** - Trivial AI (docs, commit msg, reviews, advice, etc)
|
|
54
|
+
3. **`Assisted-by: [AI Tool] <email>`** - AI helped, but primarily human code
|
|
55
|
+
4. **`Co-authored-by: [AI Tool] <email>`** - Roughly half is AI generated and half human-authored content
|
|
56
|
+
5. **`Generated-by: [AI Tool] <email>`** - Majority of code was AI generated
|
|
57
|
+
|
|
58
|
+
All patterns are case-insensitive.
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Python >= 3.11, < 3.13
|
|
63
|
+
- gitlint >= 0.19.1
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
PolyForm Shield License 1.0.0
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from gitlint.rules import CommitRule, RuleViolation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RaiFooterExists(CommitRule):
|
|
7
|
+
name = "rai-footer-exists"
|
|
8
|
+
id = "UC1"
|
|
9
|
+
target = "commit"
|
|
10
|
+
|
|
11
|
+
AI_ATTRIBUTION_KEYS = {
|
|
12
|
+
"Authored-by",
|
|
13
|
+
"Commit-generated-by",
|
|
14
|
+
"Assisted-by",
|
|
15
|
+
"Co-authored-by",
|
|
16
|
+
"Generated-by",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
AI_ATTRIBUTION_VALUE_PATTERN = re.compile(r"^[^<]+ <[^>]+>$")
|
|
20
|
+
TRAILER_PATTERN = re.compile(r"^([A-Za-z][\w-]*)\s*:\s*(.+)$")
|
|
21
|
+
|
|
22
|
+
def _parse_trailers(self, message_body):
|
|
23
|
+
if not message_body:
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
lines = message_body if isinstance(message_body, list) else message_body.split("\n")
|
|
27
|
+
trailers = {}
|
|
28
|
+
in_trailer_block = False
|
|
29
|
+
trailer_lines = []
|
|
30
|
+
|
|
31
|
+
for i in range(len(lines) - 1, -1, -1):
|
|
32
|
+
line = lines[i].strip()
|
|
33
|
+
if not line:
|
|
34
|
+
if in_trailer_block:
|
|
35
|
+
break
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
match = self.TRAILER_PATTERN.match(line)
|
|
39
|
+
if match:
|
|
40
|
+
in_trailer_block = True
|
|
41
|
+
trailer_lines.insert(0, (match.group(1), match.group(2)))
|
|
42
|
+
elif in_trailer_block:
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
for key, value in trailer_lines:
|
|
46
|
+
normalized_key = key.lower()
|
|
47
|
+
if normalized_key not in trailers:
|
|
48
|
+
trailers[normalized_key] = []
|
|
49
|
+
trailers[normalized_key].append(value.strip())
|
|
50
|
+
|
|
51
|
+
return trailers
|
|
52
|
+
|
|
53
|
+
def validate(self, commit):
|
|
54
|
+
trailers = self._parse_trailers(commit.message.body)
|
|
55
|
+
|
|
56
|
+
for key in self.AI_ATTRIBUTION_KEYS:
|
|
57
|
+
normalized_key = key.lower()
|
|
58
|
+
if normalized_key in trailers:
|
|
59
|
+
for value in trailers[normalized_key]:
|
|
60
|
+
if self.AI_ATTRIBUTION_VALUE_PATTERN.match(value):
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
RuleViolation(
|
|
65
|
+
self.id,
|
|
66
|
+
"Commit message must include AI attribution footer:\n"
|
|
67
|
+
' 1. "Authored-by: [Human] <contact>" - Human only, no AI\n'
|
|
68
|
+
' 2. "Commit-generated-by: [AI Tool] <contact>" - Trivial AI (docs, commit msg, advice)\n'
|
|
69
|
+
' 3. "Assisted-by: [AI Tool] <contact>" - AI helped, but primarily human code\n'
|
|
70
|
+
' 4. "Co-authored-by: [AI Tool] <contact>" - Roughly 50/50 AI and human (40-60 leeway)\n'
|
|
71
|
+
' 5. "Generated-by: [AI Tool] <contact>" - Majority of code was AI generated\n'
|
|
72
|
+
"\n"
|
|
73
|
+
"Examples:\n"
|
|
74
|
+
' - "Authored-by: Jane Doe <jane@example.com>"\n'
|
|
75
|
+
' - "Commit-generated-by: ChatGPT <chatgpt@openai.com>"\n'
|
|
76
|
+
' - "Assisted-by: GitHub Copilot <copilot@github.com>"\n'
|
|
77
|
+
' - "Co-authored-by: Verdent AI <verdent@verdent.ai>"\n'
|
|
78
|
+
' - "Generated-by: GitHub Copilot <copilot@github.com>"',
|
|
79
|
+
)
|
|
80
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitlint-rai
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Gitlint plugin for RAI footer validation
|
|
5
|
+
Author-email: Ashley Childress <human@checkmarkdevtools.dev>
|
|
6
|
+
License-Expression: LicenseRef-PolyForm-Shield-1.0.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ChecKMarKDevTools/rai-lint
|
|
8
|
+
Project-URL: Repository, https://github.com/ChecKMarKDevTools/rai-lint
|
|
9
|
+
Project-URL: Issues, https://github.com/ChecKMarKDevTools/rai-lint/issues
|
|
10
|
+
Keywords: gitlint,rai,ai-attribution,commit-validation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: <3.13,>=3.11
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: gitlint>=0.19.1
|
|
23
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gitlint_rai/__init__.py
|
|
5
|
+
gitlint_rai/__main__.py
|
|
6
|
+
gitlint_rai/rules.py
|
|
7
|
+
gitlint_rai.egg-info/PKG-INFO
|
|
8
|
+
gitlint_rai.egg-info/SOURCES.txt
|
|
9
|
+
gitlint_rai.egg-info/dependency_links.txt
|
|
10
|
+
gitlint_rai.egg-info/entry_points.txt
|
|
11
|
+
gitlint_rai.egg-info/requires.txt
|
|
12
|
+
gitlint_rai.egg-info/top_level.txt
|
|
13
|
+
tests/test_integration.py
|
|
14
|
+
tests/test_rules.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitlint>=0.19.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitlint_rai
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gitlint-rai"
|
|
7
|
+
version = "0.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Ashley Childress", email="human@checkmarkdevtools.dev" },
|
|
10
|
+
]
|
|
11
|
+
description = "Gitlint plugin for RAI footer validation"
|
|
12
|
+
requires-python = ">=3.11,<3.13"
|
|
13
|
+
keywords = ["gitlint", "rai", "ai-attribution", "commit-validation"]
|
|
14
|
+
dependencies = ["gitlint>=0.19.1"]
|
|
15
|
+
license = "LicenseRef-PolyForm-Shield-1.0.0"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
25
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/ChecKMarKDevTools/rai-lint"
|
|
31
|
+
Repository = "https://github.com/ChecKMarKDevTools/rai-lint"
|
|
32
|
+
Issues = "https://github.com/ChecKMarKDevTools/rai-lint/issues"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8.4.0",
|
|
39
|
+
"pytest-cov>=4.0.0",
|
|
40
|
+
"black>=25.9.0",
|
|
41
|
+
"isort>=7.0.0",
|
|
42
|
+
"coverage>=7.0.0",
|
|
43
|
+
"build>=0.10.0",
|
|
44
|
+
"wheel",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["."]
|
|
49
|
+
include = ["gitlint_rai*"]
|
|
50
|
+
|
|
51
|
+
[project.entry-points."gitlint.plugins"]
|
|
52
|
+
rai-footer-exists = "gitlint_rai.rules:RaiFooterExists"
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
gitlint-rai = "gitlint_rai.__main__:main"
|
|
56
|
+
|
|
57
|
+
[tool.coverage.run]
|
|
58
|
+
source = ["gitlint_rai"]
|
|
59
|
+
omit = [
|
|
60
|
+
"tests/*",
|
|
61
|
+
"*/test_*",
|
|
62
|
+
"*/__pycache__/*",
|
|
63
|
+
".venv/*",
|
|
64
|
+
"venv/*",
|
|
65
|
+
"gitlint_rai/__main__.py",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.coverage.report]
|
|
69
|
+
exclude_lines = [
|
|
70
|
+
"pragma: no cover",
|
|
71
|
+
"def __repr__",
|
|
72
|
+
"if self.debug:",
|
|
73
|
+
"if settings.DEBUG",
|
|
74
|
+
"raise AssertionError",
|
|
75
|
+
"raise NotImplementedError",
|
|
76
|
+
"if 0:",
|
|
77
|
+
"if __name__ == '__main__':",
|
|
78
|
+
]
|
|
79
|
+
show_missing = true
|
|
80
|
+
precision = 2
|
|
81
|
+
fail_under = 80
|
|
82
|
+
|
|
83
|
+
[tool.coverage.xml]
|
|
84
|
+
output = "reports/coverage.xml"
|
|
85
|
+
|
|
86
|
+
[tool.pytest.ini_options]
|
|
87
|
+
addopts = "--junitxml=reports/junit-py.xml"
|
|
88
|
+
testpaths = ["tests"]
|
|
89
|
+
|
|
90
|
+
[tool.black]
|
|
91
|
+
line-length = 100
|
|
92
|
+
target-version = ['py311', 'py312']
|
|
93
|
+
|
|
94
|
+
[tool.isort]
|
|
95
|
+
profile = "black"
|
|
96
|
+
line_length = 100
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
from gitlint_rai.rules import RaiFooterExists
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_commit(message: str):
|
|
7
|
+
commit = Mock()
|
|
8
|
+
commit.message.full = message
|
|
9
|
+
|
|
10
|
+
lines = message.split("\n")
|
|
11
|
+
commit.message.title = lines[0] if lines else ""
|
|
12
|
+
commit.message.body = lines[1:] if len(lines) > 1 else []
|
|
13
|
+
|
|
14
|
+
return commit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_valid_generated_by():
|
|
18
|
+
rule = RaiFooterExists()
|
|
19
|
+
commit = create_commit("feat: add feature\n\nGenerated-by: GitHub Copilot <copilot@github.com>")
|
|
20
|
+
violations = rule.validate(commit)
|
|
21
|
+
assert len(violations) == 0, f"Expected no violations but got: {violations}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_valid_assisted_by():
|
|
25
|
+
rule = RaiFooterExists()
|
|
26
|
+
commit = create_commit("fix: resolve bug\n\nAssisted-by: Verdent AI <verdent@verdent.ai>")
|
|
27
|
+
violations = rule.validate(commit)
|
|
28
|
+
assert len(violations) == 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_valid_verdent_ai():
|
|
32
|
+
rule = RaiFooterExists()
|
|
33
|
+
commit = create_commit("chore: update\n\nGenerated-by: Verdent AI <verdent@verdent.ai>")
|
|
34
|
+
violations = rule.validate(commit)
|
|
35
|
+
assert len(violations) == 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_invalid_no_footer():
|
|
39
|
+
rule = RaiFooterExists()
|
|
40
|
+
commit = create_commit("feat: add feature\n\nNo footer")
|
|
41
|
+
violations = rule.validate(commit)
|
|
42
|
+
assert len(violations) == 1
|
|
43
|
+
assert "AI attribution" in violations[0].message
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_invalid_malformed():
|
|
47
|
+
rule = RaiFooterExists()
|
|
48
|
+
commit = create_commit("feat: add feature\n\nGenerated-by: Invalid Format")
|
|
49
|
+
violations = rule.validate(commit)
|
|
50
|
+
assert len(violations) == 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_case_insensitive():
|
|
54
|
+
rule = RaiFooterExists()
|
|
55
|
+
commit = create_commit("feat: add feature\n\ngenerated-by: GitHub Copilot <copilot@github.com>")
|
|
56
|
+
violations = rule.validate(commit)
|
|
57
|
+
assert len(violations) == 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_with_additional_footers():
|
|
61
|
+
rule = RaiFooterExists()
|
|
62
|
+
commit = create_commit(
|
|
63
|
+
"feat: add feature\n\nDescription\n\nCloses #123\n\nAssisted-by: GitHub Copilot <copilot@github.com>"
|
|
64
|
+
)
|
|
65
|
+
violations = rule.validate(commit)
|
|
66
|
+
assert len(violations) == 0
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from gitlint_rai.rules import RaiFooterExists
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_commit(message: str):
|
|
9
|
+
commit = Mock()
|
|
10
|
+
commit.message.full = message
|
|
11
|
+
|
|
12
|
+
lines = message.split("\n")
|
|
13
|
+
commit.message.title = lines[0] if lines else ""
|
|
14
|
+
commit.message.body = lines[1:] if len(lines) > 1 else []
|
|
15
|
+
|
|
16
|
+
return commit
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestRaiFooterExists:
|
|
20
|
+
def test_generated_by_footer(self):
|
|
21
|
+
rule = RaiFooterExists()
|
|
22
|
+
commit = create_commit(
|
|
23
|
+
"feat: add new feature\n\nGenerated-by: GitHub Copilot <copilot@github.com>"
|
|
24
|
+
)
|
|
25
|
+
violations = rule.validate(commit)
|
|
26
|
+
assert len(violations) == 0
|
|
27
|
+
|
|
28
|
+
def test_generated_by_footer_no_email(self):
|
|
29
|
+
rule = RaiFooterExists()
|
|
30
|
+
commit = create_commit("feat: add new feature\n\nGenerated-by: GitHub Copilot")
|
|
31
|
+
violations = rule.validate(commit)
|
|
32
|
+
assert len(violations) == 1
|
|
33
|
+
|
|
34
|
+
def test_assisted_by_footer(self):
|
|
35
|
+
rule = RaiFooterExists()
|
|
36
|
+
commit = create_commit("fix: resolve bug\n\nAssisted-by: Verdent AI <verdent@verdent.ai>")
|
|
37
|
+
violations = rule.validate(commit)
|
|
38
|
+
assert len(violations) == 0
|
|
39
|
+
|
|
40
|
+
def test_co_authored_by_footer(self):
|
|
41
|
+
rule = RaiFooterExists()
|
|
42
|
+
commit = create_commit(
|
|
43
|
+
"refactor: improve code\n\nCo-authored-by: GitHub Copilot <copilot@github.com>"
|
|
44
|
+
)
|
|
45
|
+
violations = rule.validate(commit)
|
|
46
|
+
assert len(violations) == 0
|
|
47
|
+
|
|
48
|
+
def test_commit_generated_by_footer(self):
|
|
49
|
+
rule = RaiFooterExists()
|
|
50
|
+
commit = create_commit(
|
|
51
|
+
"chore: update dependencies\n\nCommit-generated-by: Claude AI <claude@anthropic.com>"
|
|
52
|
+
)
|
|
53
|
+
violations = rule.validate(commit)
|
|
54
|
+
assert len(violations) == 0
|
|
55
|
+
|
|
56
|
+
def test_authored_by_footer(self):
|
|
57
|
+
rule = RaiFooterExists()
|
|
58
|
+
commit = create_commit(
|
|
59
|
+
"feat: implement feature\n\nAuthored-by: Jane Doe <jane@example.com>"
|
|
60
|
+
)
|
|
61
|
+
violations = rule.validate(commit)
|
|
62
|
+
assert len(violations) == 0
|
|
63
|
+
|
|
64
|
+
def test_missing_ai_attribution(self):
|
|
65
|
+
rule = RaiFooterExists()
|
|
66
|
+
commit = create_commit("feat: add new feature\n\nSome other footer")
|
|
67
|
+
violations = rule.validate(commit)
|
|
68
|
+
assert len(violations) == 1
|
|
69
|
+
assert "AI attribution" in violations[0].message
|
|
70
|
+
|
|
71
|
+
def test_case_insensitive_footer(self):
|
|
72
|
+
rule = RaiFooterExists()
|
|
73
|
+
commit = create_commit(
|
|
74
|
+
"feat: add feature\n\ngenerated-by: GitHub Copilot <copilot@github.com>"
|
|
75
|
+
)
|
|
76
|
+
violations = rule.validate(commit)
|
|
77
|
+
assert len(violations) == 0
|
|
78
|
+
|
|
79
|
+
def test_malformed_footer(self):
|
|
80
|
+
# With the new rule, almost any non-empty string is valid if the key is correct.
|
|
81
|
+
# So "Generated-by: Invalid Format" is actually valid now as "Invalid Format" is a name.
|
|
82
|
+
# We should test for empty value if that's possible, or just remove this test if any string is valid.
|
|
83
|
+
# Let's assume empty value is invalid.
|
|
84
|
+
rule = RaiFooterExists()
|
|
85
|
+
commit = create_commit("feat: add feature\n\nGenerated-by: ")
|
|
86
|
+
# The simple parser might parse empty string.
|
|
87
|
+
# If regex is ^.+$ then empty string fails.
|
|
88
|
+
violations = rule.validate(commit)
|
|
89
|
+
assert len(violations) == 1
|
|
90
|
+
|
|
91
|
+
def test_multiple_ai_tools(self):
|
|
92
|
+
rule = RaiFooterExists()
|
|
93
|
+
commit = create_commit(
|
|
94
|
+
"feat: complex feature\n\nGenerated-by: ChatGPT <chatgpt@openai.com>"
|
|
95
|
+
)
|
|
96
|
+
violations = rule.validate(commit)
|
|
97
|
+
assert len(violations) == 0
|
|
98
|
+
|
|
99
|
+
def test_guidance_percentages(self):
|
|
100
|
+
rule = RaiFooterExists()
|
|
101
|
+
commit = create_commit(
|
|
102
|
+
"feat: add feature\n\nAssisted-by: GitHub Copilot <copilot@github.com>"
|
|
103
|
+
)
|
|
104
|
+
violations = rule.validate(commit)
|
|
105
|
+
assert len(violations) == 0
|