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.
@@ -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,3 @@
1
+ from gitlint_rai.rules import RaiFooterExists
2
+
3
+ __all__ = ["RaiFooterExists"]
@@ -0,0 +1,14 @@
1
+ import sys
2
+
3
+ from gitlint.cli import cli
4
+
5
+
6
+ def main():
7
+ if "--extra-path" not in sys.argv:
8
+ sys.argv.insert(1, "--extra-path")
9
+ sys.argv.insert(2, "gitlint_rai")
10
+ sys.exit(cli())
11
+
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -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,5 @@
1
+ [console_scripts]
2
+ gitlint-rai = gitlint_rai.__main__:main
3
+
4
+ [gitlint.plugins]
5
+ rai-footer-exists = gitlint_rai.rules:RaiFooterExists
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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