git-commit-msg-ai 2.0.1__tar.gz → 2.1.1__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.
Files changed (26) hide show
  1. git_commit_msg_ai-2.1.1/LICENSE +21 -0
  2. {git_commit_msg_ai-2.0.1/git_commit_msg_ai.egg-info → git_commit_msg_ai-2.1.1}/PKG-INFO +74 -4
  3. git_commit_msg_ai-2.0.1/PKG-INFO → git_commit_msg_ai-2.1.1/README.md +49 -18
  4. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/ai_client.py +18 -11
  5. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/cli.py +16 -17
  6. git_commit_msg_ai-2.1.1/git_commit_msg_ai/config.py +80 -0
  7. git_commit_msg_ai-2.1.1/git_commit_msg_ai.egg-info/PKG-INFO +231 -0
  8. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai.egg-info/SOURCES.txt +3 -0
  9. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/pyproject.toml +2 -2
  10. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_ai_client.py +26 -6
  11. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_cli.py +111 -0
  12. git_commit_msg_ai-2.1.1/tests/test_config.py +308 -0
  13. git_commit_msg_ai-2.0.1/README.md +0 -145
  14. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/__init__.py +0 -0
  15. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/editor.py +0 -0
  16. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/exceptions.py +0 -0
  17. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai/git_ops.py +0 -0
  18. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai.egg-info/dependency_links.txt +0 -0
  19. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai.egg-info/entry_points.txt +0 -0
  20. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai.egg-info/requires.txt +0 -0
  21. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/git_commit_msg_ai.egg-info/top_level.txt +0 -0
  22. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/setup.cfg +0 -0
  23. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_editor.py +0 -0
  24. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_exceptions.py +0 -0
  25. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_generate_release_notes.py +0 -0
  26. {git_commit_msg_ai-2.0.1 → git_commit_msg_ai-2.1.1}/tests/test_git_ops.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankit Joshi
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 deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,10 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-msg-ai
3
- Version: 2.0.1
3
+ Version: 2.1.1
4
4
  Summary: AI-powered git commit message generator following Conventional Commits
5
- License-Expression: MIT
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Ankit Joshi
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
6
27
  Requires-Python: >=3.14
7
28
  Description-Content-Type: text/markdown
29
+ License-File: LICENSE
8
30
  Requires-Dist: anthropic
9
31
  Provides-Extra: dev
10
32
  Requires-Dist: mypy; extra == "dev"
@@ -13,6 +35,7 @@ Requires-Dist: pytest; extra == "dev"
13
35
  Requires-Dist: pytest-cov; extra == "dev"
14
36
  Requires-Dist: build; extra == "dev"
15
37
  Requires-Dist: twine; extra == "dev"
38
+ Dynamic: license-file
16
39
 
17
40
  # git-commit-msg-ai
18
41
 
@@ -112,7 +135,50 @@ feat(api)!: remove deprecated endpoint
112
135
  BREAKING CHANGE: /v1/legacy is no longer available
113
136
  ```
114
137
 
115
- Supported types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
138
+ Supported types: `feat`, `fix`, `revert`, `build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test` (configurable — see [Configuration](#configuration))
139
+
140
+ `revert` commits always follow this format: the subject line begins with `revert: ` followed by the header of the reverted commit, and the body must contain `This reverts commit <hash>.`
141
+
142
+ ## Configuration
143
+
144
+ The set of optional commit types the AI may use can be customised through a config file. `feat`, `fix`, and `revert` are always included regardless of any configuration.
145
+
146
+ ### Config file locations and precedence
147
+
148
+ The tool checks the following locations in order, using the first one that defines a `types` list:
149
+
150
+ 1. **Project-level** — `pyproject.toml` walked up from the current working directory, under `[tool.git-commit-msg-ai]`
151
+ 2. **User-level** — `~/.git-commit-msg-ai.toml` in your home directory
152
+ 3. **Built-in defaults** — used when neither config file is present or neither defines `types`
153
+
154
+ ### Config file formats
155
+
156
+ Project-level (`pyproject.toml`):
157
+
158
+ ```toml
159
+ [tool.git-commit-msg-ai]
160
+ types = ["build", "ci", "docs", "perf", "refactor", "style", "test"]
161
+ ```
162
+
163
+ User-level (`~/.git-commit-msg-ai.toml`):
164
+
165
+ ```toml
166
+ types = ["build", "ci", "docs", "chore"]
167
+ ```
168
+
169
+ Setting `types = []` restricts the AI to only the three mandatory types (`feat`, `fix`, `revert`).
170
+
171
+ ### Default optional types
172
+
173
+ | Type | Purpose |
174
+ |---|---|
175
+ | `build` | Changes to the build system or external dependencies |
176
+ | `ci` | Changes to CI configuration files and scripts |
177
+ | `docs` | Documentation-only changes |
178
+ | `perf` | A code change that improves performance |
179
+ | `refactor` | A code change that neither fixes a bug nor adds a feature |
180
+ | `style` | Changes that do not affect the meaning of the code (whitespace, formatting) |
181
+ | `test` | Adding missing tests or correcting existing tests |
116
182
 
117
183
  ## CI/CD
118
184
 
@@ -140,7 +206,7 @@ git push origin v1.5.2
140
206
 
141
207
  ## Debugging
142
208
 
143
- By default the tool produces no diagnostic output. To enable logging, set the `GIT_COMMIT_AI_LOG_LEVEL` environment variable before running the command. Logs are written to **stderr** and do not interfere with the generated commit message on **stdout**.
209
+ When a failure occurs (git error, API error, editor error), the tool always prints an error message to **stderr**. Diagnostic logs are disabled by default; to enable them, set the `GIT_COMMIT_AI_LOG_LEVEL` environment variable before running the command. Both error messages and logs are written to **stderr** and do not interfere with the generated commit message on **stdout**.
144
210
 
145
211
  Valid values: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
146
212
 
@@ -159,3 +225,7 @@ git-commit-msg-ai
159
225
  | `INFO` | commit message generated (with char count), commit created |
160
226
  | `WARNING` | no staged changes found |
161
227
  | `ERROR` | git not found, API failures, editor errors |
228
+
229
+ ## License
230
+
231
+ This project is released under the [MIT License](LICENSE).
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: git-commit-msg-ai
3
- Version: 2.0.1
4
- Summary: AI-powered git commit message generator following Conventional Commits
5
- License-Expression: MIT
6
- Requires-Python: >=3.14
7
- Description-Content-Type: text/markdown
8
- Requires-Dist: anthropic
9
- Provides-Extra: dev
10
- Requires-Dist: mypy; extra == "dev"
11
- Requires-Dist: ruff; extra == "dev"
12
- Requires-Dist: pytest; extra == "dev"
13
- Requires-Dist: pytest-cov; extra == "dev"
14
- Requires-Dist: build; extra == "dev"
15
- Requires-Dist: twine; extra == "dev"
16
-
17
1
  # git-commit-msg-ai
18
2
 
19
3
  AI-powered git commit message generator that follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.
@@ -112,7 +96,50 @@ feat(api)!: remove deprecated endpoint
112
96
  BREAKING CHANGE: /v1/legacy is no longer available
113
97
  ```
114
98
 
115
- Supported types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
99
+ Supported types: `feat`, `fix`, `revert`, `build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test` (configurable — see [Configuration](#configuration))
100
+
101
+ `revert` commits always follow this format: the subject line begins with `revert: ` followed by the header of the reverted commit, and the body must contain `This reverts commit <hash>.`
102
+
103
+ ## Configuration
104
+
105
+ The set of optional commit types the AI may use can be customised through a config file. `feat`, `fix`, and `revert` are always included regardless of any configuration.
106
+
107
+ ### Config file locations and precedence
108
+
109
+ The tool checks the following locations in order, using the first one that defines a `types` list:
110
+
111
+ 1. **Project-level** — `pyproject.toml` walked up from the current working directory, under `[tool.git-commit-msg-ai]`
112
+ 2. **User-level** — `~/.git-commit-msg-ai.toml` in your home directory
113
+ 3. **Built-in defaults** — used when neither config file is present or neither defines `types`
114
+
115
+ ### Config file formats
116
+
117
+ Project-level (`pyproject.toml`):
118
+
119
+ ```toml
120
+ [tool.git-commit-msg-ai]
121
+ types = ["build", "ci", "docs", "perf", "refactor", "style", "test"]
122
+ ```
123
+
124
+ User-level (`~/.git-commit-msg-ai.toml`):
125
+
126
+ ```toml
127
+ types = ["build", "ci", "docs", "chore"]
128
+ ```
129
+
130
+ Setting `types = []` restricts the AI to only the three mandatory types (`feat`, `fix`, `revert`).
131
+
132
+ ### Default optional types
133
+
134
+ | Type | Purpose |
135
+ |---|---|
136
+ | `build` | Changes to the build system or external dependencies |
137
+ | `ci` | Changes to CI configuration files and scripts |
138
+ | `docs` | Documentation-only changes |
139
+ | `perf` | A code change that improves performance |
140
+ | `refactor` | A code change that neither fixes a bug nor adds a feature |
141
+ | `style` | Changes that do not affect the meaning of the code (whitespace, formatting) |
142
+ | `test` | Adding missing tests or correcting existing tests |
116
143
 
117
144
  ## CI/CD
118
145
 
@@ -140,7 +167,7 @@ git push origin v1.5.2
140
167
 
141
168
  ## Debugging
142
169
 
143
- By default the tool produces no diagnostic output. To enable logging, set the `GIT_COMMIT_AI_LOG_LEVEL` environment variable before running the command. Logs are written to **stderr** and do not interfere with the generated commit message on **stdout**.
170
+ When a failure occurs (git error, API error, editor error), the tool always prints an error message to **stderr**. Diagnostic logs are disabled by default; to enable them, set the `GIT_COMMIT_AI_LOG_LEVEL` environment variable before running the command. Both error messages and logs are written to **stderr** and do not interfere with the generated commit message on **stdout**.
144
171
 
145
172
  Valid values: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
146
173
 
@@ -159,3 +186,7 @@ git-commit-msg-ai
159
186
  | `INFO` | commit message generated (with char count), commit created |
160
187
  | `WARNING` | no staged changes found |
161
188
  | `ERROR` | git not found, API failures, editor errors |
189
+
190
+ ## License
191
+
192
+ This project is released under the [MIT License](LICENSE).
@@ -3,24 +3,31 @@ import textwrap
3
3
  from typing import Final
4
4
 
5
5
  import anthropic
6
+ from anthropic.types import CacheControlEphemeralParam, TextBlockParam
6
7
 
7
8
  from git_commit_msg_ai.exceptions import AIError
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
11
- SYSTEM_PROMPT: Final[str] = textwrap.dedent("""\
12
- You are a Git commit message generator. Output only the commit message, nothing else. Follow the Conventional Commits specification:
13
- - First line: <type>(<optional scope>)<!>: <short subject> (50 chars max); append ! before the colon if the commit introduces a breaking change
14
- - Blank line
15
- - Bullet-point body explaining WHY the changes were made, not what
16
- - If there is a breaking change, add a blank line after the body followed by "BREAKING CHANGE: <description of what breaks and why>
17
- Types: feat, fix, docs, style, refactor, test, chore""")
18
-
19
12
  MODEL: Final[str] = "claude-sonnet-4-6"
20
- MAX_TOKENS: Final[int] = 1024
13
+ MAX_TOKENS: Final[int] = 1024 # generous ceiling for any Conventional Commit message; caps API cost and output length
14
+
15
+
16
+ def _build_system_prompt(types: tuple[str, ...]) -> list[TextBlockParam]:
17
+ types_str = ", ".join(types)
18
+
19
+ text = textwrap.dedent(f"""\
20
+ You are a Git commit message generator. Output only the commit message, nothing else. Follow the Conventional Commits specification:
21
+ - First line: <type>(<optional scope>)<!>: <short subject> (50 chars max); append ! before the colon if the commit introduces a breaking change
22
+ - Blank line
23
+ - Bullet-point body explaining WHY the changes were made, not what
24
+ - If there is a breaking change, add a blank line after the body followed by "BREAKING CHANGE: <description of what breaks and why>
25
+ - For revert commits: begin the first line with "revert: " followed by the header of the reverted commit; the body must contain "This reverts commit <hash>." where <hash> is the SHA of the reverted commit
26
+ Types: {types_str}""")
27
+ return [TextBlockParam(type="text", text=text, cache_control=CacheControlEphemeralParam(type="ephemeral"))]
21
28
 
22
29
 
23
- def generate_commit_message(diff: str) -> str:
30
+ def generate_commit_message(diff: str, types: tuple[str, ...]) -> str:
24
31
  try:
25
32
  anthropic_client = anthropic.Anthropic()
26
33
 
@@ -28,7 +35,7 @@ def generate_commit_message(diff: str) -> str:
28
35
  anthropic_api_response = anthropic_client.messages.create(
29
36
  model=MODEL,
30
37
  max_tokens=MAX_TOKENS,
31
- system=SYSTEM_PROMPT,
38
+ system=_build_system_prompt(types),
32
39
  messages=[{"role": "user", "content": diff}],
33
40
  )
34
41
  except anthropic.AuthenticationError:
@@ -3,19 +3,14 @@ import os
3
3
  import sys
4
4
  from typing import Final
5
5
 
6
- from git_commit_msg_ai import ai_client, editor, git_ops
6
+ from git_commit_msg_ai import ai_client, config, editor, git_ops
7
7
  from git_commit_msg_ai.exceptions import GitCommitAIError
8
8
 
9
9
  LOG_LEVEL_ENVIRONMENT_VARIABLE: Final[str] = "GIT_COMMIT_AI_LOG_LEVEL"
10
10
  DEFAULT_LOG_LEVEL: Final[str] = "WARNING"
11
11
  LOG_FORMAT: Final[str] = "%(levelname)s:%(name)s:%(message)s"
12
- SELECTION_PROMPT: Final[str] = "[a]ccept / [e]dit / [r]eject: "
13
- SELECTION_ACCEPT: Final[str] = "a"
14
- SELECTION_EDIT: Final[str] = "e"
15
- SELECTION_REJECT: Final[str] = "r"
16
- REJECTED_MESSAGE: Final[str] = "User rejected the generated commit message. No commit made."
17
- INVALID_SELECTION_MESSAGE: Final[str] = "Invalid selection."
18
- ABORTED_MESSAGE: Final[str] = "Aborted."
12
+
13
+ logger = logging.getLogger(__name__)
19
14
 
20
15
 
21
16
  def main() -> None:
@@ -29,29 +24,33 @@ def main() -> None:
29
24
 
30
25
  logging.basicConfig(level=effective_log_level, format=LOG_FORMAT, stream=sys.stderr)
31
26
 
27
+ optional_types = config.load_optional_types()
28
+ all_types = config.get_all_types(optional_types)
29
+
32
30
  try:
33
31
  diff = git_ops.get_staged_diff()
34
- commit_message = ai_client.generate_commit_message(diff)
32
+ commit_message = ai_client.generate_commit_message(diff, all_types)
35
33
  print(commit_message)
36
34
 
37
35
  print()
38
- user_selection = input(SELECTION_PROMPT).strip().lower()
36
+ user_selection = input("[a]ccept / [e]dit / [r]eject: ").strip().lower()
39
37
 
40
- if user_selection == SELECTION_ACCEPT:
38
+ if user_selection == "a":
41
39
  git_ops.commit(commit_message)
42
- elif user_selection == SELECTION_EDIT:
40
+ elif user_selection == "e":
43
41
  updated_commit_message = editor.open_in_editor(commit_message)
44
42
  git_ops.commit(updated_commit_message)
45
- elif user_selection == SELECTION_REJECT:
46
- print(REJECTED_MESSAGE)
43
+ elif user_selection == "r":
44
+ print("User rejected the generated commit message. No commit made.")
47
45
  else:
48
- print(INVALID_SELECTION_MESSAGE)
46
+ print("Invalid selection.")
49
47
  sys.exit(1)
50
48
  except GitCommitAIError as error:
51
- logging.error(str(error))
49
+ logger.error(str(error))
50
+ print(f"Error: {error}", file=sys.stderr)
52
51
  sys.exit(1)
53
52
  except KeyboardInterrupt, EOFError:
54
- print(ABORTED_MESSAGE)
53
+ print("Aborted.")
55
54
  sys.exit(1)
56
55
 
57
56
 
@@ -0,0 +1,80 @@
1
+ import logging
2
+ import tomllib
3
+ from pathlib import Path
4
+ from typing import Final
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ MANDATORY_TYPES: Final[tuple[str, ...]] = ("feat", "fix", "revert")
9
+ DEFAULT_OPTIONAL_TYPES: Final[tuple[str, ...]] = ("build", "ci", "docs", "perf", "refactor", "style", "test")
10
+
11
+
12
+ def _find_pyproject_toml() -> Path | None:
13
+ for directory in [Path.cwd(), *Path.cwd().parents]:
14
+ candidate = directory / "pyproject.toml"
15
+ if candidate.is_file():
16
+ return candidate
17
+
18
+ return None
19
+
20
+
21
+ def _parse_types_list(types_value: object, source: str) -> tuple[str, ...] | None:
22
+ if types_value is None:
23
+ return None
24
+ if not isinstance(types_value, list):
25
+ logger.warning(f"'types' in {source} must be a TOML array, skipping")
26
+ return None
27
+
28
+ return tuple(entry.strip().lower() for entry in types_value if isinstance(entry, str) and entry.strip())
29
+
30
+
31
+ def _load_from_pyproject_toml(path: Path) -> tuple[str, ...] | None:
32
+ try:
33
+ with open(path, "rb") as toml_file:
34
+ data = tomllib.load(toml_file)
35
+ except tomllib.TOMLDecodeError:
36
+ logger.warning(f"Could not parse {path}, trying next config source")
37
+ return None
38
+
39
+ tool_section = data.get("tool")
40
+ if not isinstance(tool_section, dict):
41
+ return None
42
+
43
+ pkg_section = tool_section.get("git-commit-msg-ai")
44
+ if not isinstance(pkg_section, dict):
45
+ return None
46
+
47
+ return _parse_types_list(pkg_section.get("types"), f"[tool.git-commit-msg-ai] in {path}")
48
+
49
+
50
+ def _load_from_global_config() -> tuple[str, ...] | None:
51
+ global_config = Path.home() / ".git-commit-msg-ai.toml"
52
+ if not global_config.is_file():
53
+ return None
54
+
55
+ try:
56
+ with open(global_config, "rb") as toml_file:
57
+ data = tomllib.load(toml_file)
58
+ except tomllib.TOMLDecodeError:
59
+ logger.warning(f"Could not parse {global_config}, using default commit types")
60
+ return None
61
+
62
+ return _parse_types_list(data.get("types"), str(global_config))
63
+
64
+
65
+ def load_optional_types() -> tuple[str, ...]:
66
+ pyproject_path = _find_pyproject_toml()
67
+ if pyproject_path is not None:
68
+ result = _load_from_pyproject_toml(pyproject_path)
69
+ if result is not None:
70
+ return result
71
+
72
+ result = _load_from_global_config()
73
+ if result is not None:
74
+ return result
75
+
76
+ return DEFAULT_OPTIONAL_TYPES
77
+
78
+
79
+ def get_all_types(optional_types: tuple[str, ...]) -> tuple[str, ...]:
80
+ return tuple(dict.fromkeys(MANDATORY_TYPES + optional_types))
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-commit-msg-ai
3
+ Version: 2.1.1
4
+ Summary: AI-powered git commit message generator following Conventional Commits
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Ankit Joshi
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.14
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: anthropic
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Requires-Dist: pytest; extra == "dev"
35
+ Requires-Dist: pytest-cov; extra == "dev"
36
+ Requires-Dist: build; extra == "dev"
37
+ Requires-Dist: twine; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # git-commit-msg-ai
41
+
42
+ AI-powered git commit message generator that follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.
43
+
44
+ ## Prerequisites
45
+
46
+ - Python 3.14+
47
+ - An Anthropic API key set as an environment variable:
48
+
49
+ ```sh
50
+ export ANTHROPIC_API_KEY=sk-ant-... # macOS/Linux
51
+ ```
52
+
53
+ ```powershell
54
+ [System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-ant-...', 'User') # Windows
55
+ ```
56
+
57
+ ## Development Setup
58
+
59
+ Clone the repository, create a virtual environment, and install the project in editable mode with all dev dependencies:
60
+
61
+ ```sh
62
+ python -m venv .venv
63
+ ```
64
+
65
+ Activate the virtual environment:
66
+
67
+ ```sh
68
+ # macOS/Linux
69
+ source .venv/bin/activate
70
+
71
+ # Windows PowerShell
72
+ .venv\Scripts\Activate.ps1
73
+ ```
74
+
75
+ Install the project and dev dependencies:
76
+
77
+ ```sh
78
+ pip install -e ".[dev]"
79
+ ```
80
+
81
+ After activation the `git-commit-msg-ai` entry-point is on your PATH. You can also run the dev toolchain:
82
+
83
+ ```sh
84
+ pytest # run tests with coverage
85
+ ruff check . # lint
86
+ mypy . # type-check
87
+ ```
88
+
89
+ ## Installation
90
+
91
+ ```sh
92
+ pip install git-commit-msg-ai
93
+ ```
94
+
95
+ ## Usage
96
+
97
+ Stage your changes, then run the tool from inside any git repository:
98
+
99
+ ```sh
100
+ git add <files>
101
+ git-commit-msg-ai
102
+ ```
103
+
104
+ The tool will:
105
+ 1. Read your staged diff
106
+ 2. Generate a commit message using Claude AI
107
+ 3. Print the message and prompt you to choose:
108
+
109
+ ```
110
+ [a]ccept / [e]dit / [r]eject:
111
+ ```
112
+
113
+ - **a** - commits immediately with the generated message
114
+ - **e** - opens the message in your `$EDITOR` (defaults to `notepad` on Windows, `vi` on Linux/macOS), lets you modify it, then commits
115
+ - **r** - exits without committing
116
+
117
+ ## Commit message format
118
+
119
+ Generated messages follow the Conventional Commits specification:
120
+
121
+ ```
122
+ <type>(<optional scope>): <short subject>
123
+
124
+ - Bullet explaining why this change was made
125
+ - Another reason if applicable
126
+ ```
127
+
128
+ For breaking changes, the subject line gets a `!` and a footer is added:
129
+
130
+ ```
131
+ feat(api)!: remove deprecated endpoint
132
+
133
+ - Endpoint was unused and blocking the new auth rollout
134
+
135
+ BREAKING CHANGE: /v1/legacy is no longer available
136
+ ```
137
+
138
+ Supported types: `feat`, `fix`, `revert`, `build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test` (configurable — see [Configuration](#configuration))
139
+
140
+ `revert` commits always follow this format: the subject line begins with `revert: ` followed by the header of the reverted commit, and the body must contain `This reverts commit <hash>.`
141
+
142
+ ## Configuration
143
+
144
+ The set of optional commit types the AI may use can be customised through a config file. `feat`, `fix`, and `revert` are always included regardless of any configuration.
145
+
146
+ ### Config file locations and precedence
147
+
148
+ The tool checks the following locations in order, using the first one that defines a `types` list:
149
+
150
+ 1. **Project-level** — `pyproject.toml` walked up from the current working directory, under `[tool.git-commit-msg-ai]`
151
+ 2. **User-level** — `~/.git-commit-msg-ai.toml` in your home directory
152
+ 3. **Built-in defaults** — used when neither config file is present or neither defines `types`
153
+
154
+ ### Config file formats
155
+
156
+ Project-level (`pyproject.toml`):
157
+
158
+ ```toml
159
+ [tool.git-commit-msg-ai]
160
+ types = ["build", "ci", "docs", "perf", "refactor", "style", "test"]
161
+ ```
162
+
163
+ User-level (`~/.git-commit-msg-ai.toml`):
164
+
165
+ ```toml
166
+ types = ["build", "ci", "docs", "chore"]
167
+ ```
168
+
169
+ Setting `types = []` restricts the AI to only the three mandatory types (`feat`, `fix`, `revert`).
170
+
171
+ ### Default optional types
172
+
173
+ | Type | Purpose |
174
+ |---|---|
175
+ | `build` | Changes to the build system or external dependencies |
176
+ | `ci` | Changes to CI configuration files and scripts |
177
+ | `docs` | Documentation-only changes |
178
+ | `perf` | A code change that improves performance |
179
+ | `refactor` | A code change that neither fixes a bug nor adds a feature |
180
+ | `style` | Changes that do not affect the meaning of the code (whitespace, formatting) |
181
+ | `test` | Adding missing tests or correcting existing tests |
182
+
183
+ ## CI/CD
184
+
185
+ Every push to `main` and every pull request runs the full CI pipeline (lint, type-check, tests, build).
186
+
187
+ Pushing a version tag (e.g. `v1.5.2`) triggers the CD pipeline:
188
+
189
+ 1. Tests are re-run as a gate
190
+ 2. The tag version is verified to match the version in `pyproject.toml`
191
+ 3. The package is built and published to [PyPI](https://pypi.org/project/git-commit-msg-ai/)
192
+ 4. A GitHub Release is created with release notes generated by Claude Sonnet from the commit log
193
+
194
+ **Releasing a new version:**
195
+
196
+ ```powershell
197
+ # 1. Bump the version in pyproject.toml
198
+ # 2. Commit and push
199
+ git add pyproject.toml
200
+ git commit -m "chore: bump version to 1.5.2"
201
+ git push origin main
202
+ # 3. Tag and push - this triggers the CD pipeline
203
+ git tag v1.5.2
204
+ git push origin v1.5.2
205
+ ```
206
+
207
+ ## Debugging
208
+
209
+ When a failure occurs (git error, API error, editor error), the tool always prints an error message to **stderr**. Diagnostic logs are disabled by default; to enable them, set the `GIT_COMMIT_AI_LOG_LEVEL` environment variable before running the command. Both error messages and logs are written to **stderr** and do not interfere with the generated commit message on **stdout**.
210
+
211
+ Valid values: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
212
+
213
+ ```sh
214
+ # macOS/Linux - show all internal diagnostic messages
215
+ GIT_COMMIT_AI_LOG_LEVEL=DEBUG git-commit-msg-ai
216
+
217
+ # Windows PowerShell
218
+ $env:GIT_COMMIT_AI_LOG_LEVEL = 'DEBUG'
219
+ git-commit-msg-ai
220
+ ```
221
+
222
+ | Level | What you see |
223
+ |---|---|
224
+ | `DEBUG` | git commands run, API model/token params, temp file paths, char counts |
225
+ | `INFO` | commit message generated (with char count), commit created |
226
+ | `WARNING` | no staged changes found |
227
+ | `ERROR` | git not found, API failures, editor errors |
228
+
229
+ ## License
230
+
231
+ This project is released under the [MIT License](LICENSE).
@@ -1,8 +1,10 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  git_commit_msg_ai/__init__.py
4
5
  git_commit_msg_ai/ai_client.py
5
6
  git_commit_msg_ai/cli.py
7
+ git_commit_msg_ai/config.py
6
8
  git_commit_msg_ai/editor.py
7
9
  git_commit_msg_ai/exceptions.py
8
10
  git_commit_msg_ai/git_ops.py
@@ -14,6 +16,7 @@ git_commit_msg_ai.egg-info/requires.txt
14
16
  git_commit_msg_ai.egg-info/top_level.txt
15
17
  tests/test_ai_client.py
16
18
  tests/test_cli.py
19
+ tests/test_config.py
17
20
  tests/test_editor.py
18
21
  tests/test_exceptions.py
19
22
  tests/test_generate_release_notes.py
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "git-commit-msg-ai"
7
- version = "2.0.1"
7
+ version = "2.1.1"
8
8
  description = "AI-powered git commit message generator following Conventional Commits"
9
9
  readme = "README.md"
10
- license = "MIT"
10
+ license = {file = "LICENSE"}
11
11
  requires-python = ">=3.14"
12
12
  dependencies = ["anthropic"]
13
13