git-cai-cli 0.15.3__tar.gz → 0.15.4__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.
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/ci/cai_config.ci.yml +4 -5
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/workflows/python-tests.yml +6 -6
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/workflows/release.yml +3 -3
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/PKG-INFO +1 -1
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/cai_config.yml +4 -5
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/docs/git-cai.txt +13 -11
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/docs/man/git-cai.1 +15 -13
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/_version.py +3 -3
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/config.py +4 -4
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/doctor.py +2 -2
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/llm.py +82 -26
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/validate.py +2 -2
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
- git_cai_cli-0.15.4/src/git_cai_cli.egg-info/scm_version.json +8 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_llm.py +182 -12
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_validate.py +20 -2
- git_cai_cli-0.15.3/src/git_cai_cli.egg-info/scm_version.json +0 -8
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.caiignore +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.gitattributes +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/cd/.SRCINFO +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/cd/PKGBUILD +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/ci/_version.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/ci/tokens.ci.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.github/workflows/release_aur.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.gitignore +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.bandit.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.checkov.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.flake8 +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.ls-lint.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.markdown-link-check.json +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.markdownlint.json +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.proselintrc +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.pylintrc +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/.yamllint.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/check_git_branch_name.sh +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/lychee.toml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.linters/pyrightconfig.json +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.markdownlintignore +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.mega-linter.yml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.semgrepignore +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/.trivyignore +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/CLAUDE.md +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/LICENSE +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/Makefile +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/README.md +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/pyproject.toml +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/setup.cfg +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/__init__.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/cli/__init__.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/cli/cli.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/cli/helptext.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/cli/modes.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/__init__.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/completion.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/editors.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/gitutils.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/init.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/languages.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/options.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/pr.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/prompts_fallback.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/secrets.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/spinner.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/squash.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/core/stats.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli/main.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/requires.txt +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/src/git_cai_cli.egg-info/top_level.txt +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/conftest.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_cli_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_config_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_gitutils_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_modes_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_options_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_pr_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/integration/test_squash_integration.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_amend.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_branch_context.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_classification.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_cli.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_completion.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_config.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_conventional.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_doctor.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_gitutils.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_helptext.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_init.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_main.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_modes.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_options.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_pr.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_print.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_prompt_loading.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_secrets.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_set_config.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_signoff.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_spinner.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_squash.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/tests/unit/test_stats.py +0 -0
- {git_cai_cli-0.15.3 → git_cai_cli-0.15.4}/uv.lock +0 -0
|
@@ -13,10 +13,10 @@ deepseek:
|
|
|
13
13
|
model: deepseek-chat
|
|
14
14
|
temperature: 0
|
|
15
15
|
gemini:
|
|
16
|
-
model: gemini-
|
|
16
|
+
model: gemini-3.1-flash-lite
|
|
17
17
|
temperature: 0
|
|
18
18
|
groq:
|
|
19
|
-
model:
|
|
19
|
+
model: openai/gpt-oss-20b
|
|
20
20
|
temperature: 0
|
|
21
21
|
mistral:
|
|
22
22
|
model: codestral-2508
|
|
@@ -26,8 +26,7 @@ ollama:
|
|
|
26
26
|
temperature: 0
|
|
27
27
|
timeout: 300
|
|
28
28
|
openai:
|
|
29
|
-
model: gpt-5.
|
|
30
|
-
temperature: 0
|
|
29
|
+
model: gpt-5.4-mini
|
|
31
30
|
xai:
|
|
32
|
-
model: grok-4
|
|
31
|
+
model: grok-4.3
|
|
33
32
|
temperature: 0
|
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
17
|
- name: Checkout code
|
|
18
|
-
uses: actions/checkout@v4
|
|
18
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
19
19
|
with:
|
|
20
20
|
ref: ${{ github.head_ref }}
|
|
21
21
|
|
|
@@ -35,10 +35,10 @@ jobs:
|
|
|
35
35
|
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
36
36
|
|
|
37
37
|
steps:
|
|
38
|
-
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
39
39
|
|
|
40
40
|
- name: Set up Python ${{ matrix.python-version }}
|
|
41
|
-
uses: actions/setup-python@v4
|
|
41
|
+
uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
|
|
42
42
|
with:
|
|
43
43
|
python-version: ${{ matrix.python-version }}
|
|
44
44
|
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
run: uv venv
|
|
50
50
|
|
|
51
51
|
- name: Restore cache uv
|
|
52
|
-
uses: actions/cache@v4
|
|
52
|
+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
|
53
53
|
with:
|
|
54
54
|
path: |
|
|
55
55
|
~/.cache/uv
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
steps:
|
|
90
90
|
# Git Checkout
|
|
91
91
|
- name: Checkout Code
|
|
92
|
-
uses: actions/checkout@v6
|
|
92
|
+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
93
93
|
with:
|
|
94
94
|
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
|
|
95
95
|
fetch-depth: 0
|
|
@@ -108,7 +108,7 @@ jobs:
|
|
|
108
108
|
# Upload MegaLinter artifacts
|
|
109
109
|
- name: Archive production artifacts
|
|
110
110
|
if: success() || failure()
|
|
111
|
-
uses: actions/upload-artifact@v4
|
|
111
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
112
112
|
with:
|
|
113
113
|
name: MegaLinter reports
|
|
114
114
|
include-hidden-files: "true"
|
|
@@ -18,12 +18,12 @@ jobs:
|
|
|
18
18
|
|
|
19
19
|
steps:
|
|
20
20
|
- name: Checkout tagged commit
|
|
21
|
-
uses: actions/checkout@v4
|
|
21
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
22
22
|
with:
|
|
23
23
|
ref: ${{ github.ref }}
|
|
24
24
|
|
|
25
25
|
- name: Set up Python
|
|
26
|
-
uses: actions/setup-python@v5
|
|
26
|
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
27
27
|
with:
|
|
28
28
|
python-version: "3.13"
|
|
29
29
|
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
run: uv venv
|
|
35
35
|
|
|
36
36
|
- name: Restore uv cache
|
|
37
|
-
uses: actions/cache@v4
|
|
37
|
+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
|
38
38
|
with:
|
|
39
39
|
path: |
|
|
40
40
|
~/.cache/uv
|
|
@@ -24,10 +24,10 @@ deepseek:
|
|
|
24
24
|
model: deepseek-chat
|
|
25
25
|
temperature: 0
|
|
26
26
|
gemini:
|
|
27
|
-
model: gemini-
|
|
27
|
+
model: gemini-3.1-flash-lite
|
|
28
28
|
temperature: 0
|
|
29
29
|
groq:
|
|
30
|
-
model:
|
|
30
|
+
model: openai/gpt-oss-20b
|
|
31
31
|
temperature: 0
|
|
32
32
|
mistral:
|
|
33
33
|
model: codestral-2508
|
|
@@ -37,8 +37,7 @@ ollama:
|
|
|
37
37
|
temperature: 0
|
|
38
38
|
timeout: 300
|
|
39
39
|
openai:
|
|
40
|
-
model: gpt-5.4
|
|
41
|
-
temperature: 0
|
|
40
|
+
model: gpt-5.4-mini
|
|
42
41
|
xai:
|
|
43
|
-
model: grok-4
|
|
42
|
+
model: grok-4.3
|
|
44
43
|
temperature: 0
|
|
@@ -204,7 +204,9 @@ git cai -d
|
|
|
204
204
|
Override the sampling temperature of the active provider for this invocation
|
|
205
205
|
only. Higher values produce more varied wording; lower values are more
|
|
206
206
|
deterministic. The override is provider-scoped, mirroring `--model`, and does
|
|
207
|
-
not change the persisted `cai_config.yml`.
|
|
207
|
+
not change the persisted `cai_config.yml`. Some models no longer accept a
|
|
208
|
+
temperature (for example OpenAI GPT-5 models and Anthropic Opus 4.7 and newer);
|
|
209
|
+
for those the value is ignored and a warning is logged.
|
|
208
210
|
+
|
|
209
211
|
----
|
|
210
212
|
git cai -e 0.7
|
|
@@ -326,7 +328,7 @@ git cai -i
|
|
|
326
328
|
-k, --check::
|
|
327
329
|
Run configuration diagnostics and exit. The check is offline by default: it
|
|
328
330
|
reports which config is authoritative (repository, home, or built-in), validates
|
|
329
|
-
the config keys, confirms the default provider has a model
|
|
331
|
+
the config keys, confirms the default provider has a model and a
|
|
330
332
|
usable token, that the commit/squash/full-files/PR prompts resolve, and that the
|
|
331
333
|
configured editor is on `PATH`. Exits non-zero if any check fails. Add `--ping`
|
|
332
334
|
to also probe the active provider with a tiny live request.
|
|
@@ -734,7 +736,7 @@ Active configuration:
|
|
|
734
736
|
style: professional
|
|
735
737
|
emoji: true
|
|
736
738
|
groq:
|
|
737
|
-
model:
|
|
739
|
+
model: openai/gpt-oss-20b
|
|
738
740
|
temperature: 0
|
|
739
741
|
...
|
|
740
742
|
----
|
|
@@ -786,12 +788,12 @@ Default models:
|
|
|
786
788
|
|
|
787
789
|
anthropic → claude-haiku-4-5
|
|
788
790
|
deepseek → deepseek-chat
|
|
789
|
-
gemini → gemini-
|
|
790
|
-
groq →
|
|
791
|
+
gemini → gemini-3.1-flash-lite
|
|
792
|
+
groq → openai/gpt-oss-20b
|
|
791
793
|
mistral → codestral-2508
|
|
792
794
|
ollama → llama3.1
|
|
793
|
-
openai → gpt-5.
|
|
794
|
-
xai → grok-4
|
|
795
|
+
openai → gpt-5.4-mini
|
|
796
|
+
xai → grok-4.3
|
|
795
797
|
----
|
|
796
798
|
|
|
797
799
|
path::
|
|
@@ -849,12 +851,12 @@ Supported providers:
|
|
|
849
851
|
|
|
850
852
|
anthropic model: claude-haiku-4-5 (token required)
|
|
851
853
|
deepseek model: deepseek-chat (token required)
|
|
852
|
-
gemini model: gemini-
|
|
853
|
-
groq model:
|
|
854
|
+
gemini model: gemini-3.1-flash-lite (token required)
|
|
855
|
+
groq model: openai/gpt-oss-20b (token required)
|
|
854
856
|
mistral model: codestral-2508 (token required)
|
|
855
857
|
ollama model: llama3.1 (no token required)
|
|
856
|
-
openai model: gpt-5.
|
|
857
|
-
xai model: grok-4
|
|
858
|
+
openai model: gpt-5.4-mini (token required)
|
|
859
|
+
xai model: grok-4.3 (token required)
|
|
858
860
|
----
|
|
859
861
|
|
|
860
862
|
style::
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
.\" Title: git-cai
|
|
3
3
|
.\" Author: Thorsten Foltz
|
|
4
4
|
.\" Generator: Asciidoctor 2.0.26
|
|
5
|
-
.\" Date: 2026-
|
|
5
|
+
.\" Date: 2026-07-05
|
|
6
6
|
.\" Manual: \ \&
|
|
7
7
|
.\" Source: \ \&
|
|
8
8
|
.\" Language: English
|
|
9
9
|
.\"
|
|
10
|
-
.TH "GIT\-CAI" "1" "2026-
|
|
10
|
+
.TH "GIT\-CAI" "1" "2026-07-05" "\ \&" "\ \&"
|
|
11
11
|
.ie \n(.g .ds Aq \(aq
|
|
12
12
|
.el .ds Aq '
|
|
13
13
|
.ss \n[.ss] 0
|
|
@@ -380,7 +380,9 @@ git cai \-d
|
|
|
380
380
|
Override the sampling temperature of the active provider for this invocation
|
|
381
381
|
only. Higher values produce more varied wording; lower values are more
|
|
382
382
|
deterministic. The override is provider\-scoped, mirroring \f(CR\-\-model\fP, and does
|
|
383
|
-
not change the persisted \f(CRcai_config.yml\fP.
|
|
383
|
+
not change the persisted \f(CRcai_config.yml\fP. Some models no longer accept a
|
|
384
|
+
temperature (for example OpenAI GPT\-5 models and Anthropic Opus 4.7 and newer);
|
|
385
|
+
for those the value is ignored and a warning is logged.
|
|
384
386
|
.sp
|
|
385
387
|
.if n .RS 4
|
|
386
388
|
.nf
|
|
@@ -554,7 +556,7 @@ git cai \-i
|
|
|
554
556
|
.RS 4
|
|
555
557
|
Run configuration diagnostics and exit. The check is offline by default: it
|
|
556
558
|
reports which config is authoritative (repository, home, or built\-in), validates
|
|
557
|
-
the config keys, confirms the default provider has a model
|
|
559
|
+
the config keys, confirms the default provider has a model and a
|
|
558
560
|
usable token, that the commit/squash/full\-files/PR prompts resolve, and that the
|
|
559
561
|
configured editor is on \f(CRPATH\fP. Exits non\-zero if any check fails. Add \f(CR\-\-ping\fP
|
|
560
562
|
to also probe the active provider with a tiny live request.
|
|
@@ -1117,7 +1119,7 @@ Active configuration:
|
|
|
1117
1119
|
style: professional
|
|
1118
1120
|
emoji: true
|
|
1119
1121
|
groq:
|
|
1120
|
-
model:
|
|
1122
|
+
model: openai/gpt\-oss\-20b
|
|
1121
1123
|
temperature: 0
|
|
1122
1124
|
...
|
|
1123
1125
|
.fam
|
|
@@ -1191,12 +1193,12 @@ Default models:
|
|
|
1191
1193
|
|
|
1192
1194
|
anthropic\& → claude\-haiku\-4\-5
|
|
1193
1195
|
deepseek\& → deepseek\-chat
|
|
1194
|
-
gemini\& → gemini\-
|
|
1195
|
-
groq\& →
|
|
1196
|
+
gemini\& → gemini\-3.1\-flash\-lite
|
|
1197
|
+
groq\& → openai/gpt\-oss\-20b
|
|
1196
1198
|
mistral\& → codestral\-2508
|
|
1197
1199
|
ollama\& → llama3.1
|
|
1198
|
-
openai\& → gpt\-5.
|
|
1199
|
-
xai\& → grok\-4
|
|
1200
|
+
openai\& → gpt\-5.4\-mini
|
|
1201
|
+
xai\& → grok\-4.3
|
|
1200
1202
|
.fam
|
|
1201
1203
|
.fi
|
|
1202
1204
|
.if n .RE
|
|
@@ -1278,12 +1280,12 @@ Supported providers:
|
|
|
1278
1280
|
|
|
1279
1281
|
anthropic\& model: claude\-haiku\-4\-5\& (token required)
|
|
1280
1282
|
deepseek\& model: deepseek\-chat\& (token required)
|
|
1281
|
-
gemini\& model: gemini\-
|
|
1282
|
-
groq\& model:
|
|
1283
|
+
gemini\& model: gemini\-3.1\-flash\-lite\& (token required)
|
|
1284
|
+
groq\& model: openai/gpt\-oss\-20b\& (token required)
|
|
1283
1285
|
mistral\& model: codestral\-2508\& (token required)
|
|
1284
1286
|
ollama\& model: llama3.1\& (no token required)
|
|
1285
|
-
openai\& model: gpt\-5.
|
|
1286
|
-
xai\& model: grok\-4
|
|
1287
|
+
openai\& model: gpt\-5.4\-mini\& (token required)
|
|
1288
|
+
xai\& model: grok\-4.3\& (token required)
|
|
1287
1289
|
.fam
|
|
1288
1290
|
.fi
|
|
1289
1291
|
.if n .RE
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.15.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 15,
|
|
21
|
+
__version__ = version = '0.15.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 15, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gb94513fc4'
|
|
@@ -39,11 +39,11 @@ DEFAULT_CONFIG: dict[str, Any] = {
|
|
|
39
39
|
"temperature": 0,
|
|
40
40
|
"max_tokens": 32768,
|
|
41
41
|
},
|
|
42
|
-
"openai": {"model": "gpt-5.
|
|
42
|
+
"openai": {"model": "gpt-5.4-mini"},
|
|
43
43
|
"deepseek": {"model": "deepseek-chat", "temperature": 0},
|
|
44
|
-
"gemini": {"model": "gemini-
|
|
45
|
-
"groq": {"model": "
|
|
46
|
-
"xai": {"model": "grok-4
|
|
44
|
+
"gemini": {"model": "gemini-3.1-flash-lite", "temperature": 0},
|
|
45
|
+
"groq": {"model": "openai/gpt-oss-20b", "temperature": 0},
|
|
46
|
+
"xai": {"model": "grok-4.3", "temperature": 0},
|
|
47
47
|
"mistral": {"model": "codestral-2508", "temperature": 0},
|
|
48
48
|
"ollama": {"model": "llama3.1", "temperature": 0, "timeout": 300},
|
|
49
49
|
"language": "en",
|
|
@@ -81,8 +81,8 @@ def _run_check_impl(*, live: bool = False) -> int:
|
|
|
81
81
|
if not provider:
|
|
82
82
|
_line(_FAIL, "No default provider set")
|
|
83
83
|
ok = False
|
|
84
|
-
elif not isinstance(block, dict) or
|
|
85
|
-
_line(_FAIL, f"Provider '{provider}' block missing model
|
|
84
|
+
elif not isinstance(block, dict) or "model" not in block:
|
|
85
|
+
_line(_FAIL, f"Provider '{provider}' block missing model")
|
|
86
86
|
ok = False
|
|
87
87
|
else:
|
|
88
88
|
_line(_OK, f"Default provider: {provider} (model {block.get('model')})")
|
|
@@ -5,6 +5,7 @@ Use LLMs to generate git commit messages from diffs or multiple commits.
|
|
|
5
5
|
import functools
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import shutil
|
|
9
10
|
import signal
|
|
10
11
|
import subprocess
|
|
@@ -32,6 +33,40 @@ from urllib3.util.retry import Retry
|
|
|
32
33
|
log = logging.getLogger(__name__)
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
# Models that reject any non-default ``temperature`` (return HTTP 400).
|
|
37
|
+
# Extend this list as new restricted models appear.
|
|
38
|
+
_TEMPERATURE_UNSUPPORTED_PATTERNS = [
|
|
39
|
+
re.compile(r"^gpt-5", re.IGNORECASE), # OpenAI GPT-5 family (incl. mini/nano)
|
|
40
|
+
re.compile(r"^o[134](-|$)", re.IGNORECASE), # OpenAI o1/o3/o4 reasoning series
|
|
41
|
+
re.compile(r"opus-4-[7-9]", re.IGNORECASE), # Anthropic Opus 4.7 / 4.8 / 4.9
|
|
42
|
+
re.compile(r"opus-[5-9]", re.IGNORECASE), # Anthropic Opus 5+
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _model_rejects_temperature(model: str) -> bool:
|
|
47
|
+
"""Return True if ``model`` rejects a non-default temperature."""
|
|
48
|
+
return any(pattern.search(model) for pattern in _TEMPERATURE_UNSUPPORTED_PATTERNS)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _resolve_temperature(model: str, temperature: float | int | None) -> float | None:
|
|
52
|
+
"""Resolve the temperature to send for ``model``.
|
|
53
|
+
|
|
54
|
+
Returns ``None`` (send no temperature) when unset or when the model
|
|
55
|
+
rejects it, warning in the latter case. Otherwise returns the float.
|
|
56
|
+
"""
|
|
57
|
+
if temperature is None:
|
|
58
|
+
return None
|
|
59
|
+
if _model_rejects_temperature(model):
|
|
60
|
+
log.warning(
|
|
61
|
+
"Model '%s' does not support temperature; "
|
|
62
|
+
"ignoring configured temperature=%s.",
|
|
63
|
+
model,
|
|
64
|
+
temperature,
|
|
65
|
+
)
|
|
66
|
+
return None
|
|
67
|
+
return float(temperature)
|
|
68
|
+
|
|
69
|
+
|
|
35
70
|
_RETRY_STATUS_CODES = (429, 500, 502, 503, 504)
|
|
36
71
|
_RETRY_TOTAL = 3
|
|
37
72
|
_RETRY_BACKOFF_FACTOR = 0.5
|
|
@@ -242,7 +277,8 @@ class CommitMessageGenerator:
|
|
|
242
277
|
if isinstance(block, dict):
|
|
243
278
|
temp = block.get("temperature")
|
|
244
279
|
if isinstance(temp, (int, float)):
|
|
245
|
-
|
|
280
|
+
model_name = block.get("model", "")
|
|
281
|
+
temperature_val = _resolve_temperature(str(model_name), temp)
|
|
246
282
|
|
|
247
283
|
# Full-files mode swaps the commit prompt file for a dedicated
|
|
248
284
|
# one — surface that when active, regardless of kind.
|
|
@@ -811,7 +847,9 @@ class CommitMessageGenerator:
|
|
|
811
847
|
}
|
|
812
848
|
|
|
813
849
|
model = self.config["anthropic"]["model"]
|
|
814
|
-
temperature =
|
|
850
|
+
temperature = _resolve_temperature(
|
|
851
|
+
model, self.config["anthropic"].get("temperature")
|
|
852
|
+
)
|
|
815
853
|
# ``max_output_tokens`` is the canonical config key (consistent
|
|
816
854
|
# naming across providers); ``max_tokens`` is kept for backwards
|
|
817
855
|
# compatibility with existing user configs.
|
|
@@ -829,9 +867,10 @@ class CommitMessageGenerator:
|
|
|
829
867
|
request: dict[str, Any] = {
|
|
830
868
|
"model": model,
|
|
831
869
|
"max_tokens": max_tokens,
|
|
832
|
-
"temperature": temperature,
|
|
833
870
|
"messages": messages,
|
|
834
871
|
}
|
|
872
|
+
if temperature is not None:
|
|
873
|
+
request["temperature"] = temperature
|
|
835
874
|
|
|
836
875
|
if system_prompt_override:
|
|
837
876
|
request["system"] = system_prompt_override
|
|
@@ -865,7 +904,7 @@ class CommitMessageGenerator:
|
|
|
865
904
|
"""
|
|
866
905
|
url = "https://api.deepseek.com"
|
|
867
906
|
model = self.config["deepseek"]["model"]
|
|
868
|
-
temperature = self.config["deepseek"]
|
|
907
|
+
temperature = self.config["deepseek"].get("temperature")
|
|
869
908
|
return self.generate_openai(
|
|
870
909
|
content=content,
|
|
871
910
|
system_prompt_override=system_prompt_override,
|
|
@@ -885,7 +924,9 @@ class CommitMessageGenerator:
|
|
|
885
924
|
Uses direct HTTP API instead of the Google SDK.
|
|
886
925
|
"""
|
|
887
926
|
model = self.config["gemini"]["model"]
|
|
888
|
-
temperature =
|
|
927
|
+
temperature = _resolve_temperature(
|
|
928
|
+
model, self.config["gemini"].get("temperature")
|
|
929
|
+
)
|
|
889
930
|
|
|
890
931
|
log.debug("Using gemini model '%s'.", model)
|
|
891
932
|
|
|
@@ -900,11 +941,12 @@ class CommitMessageGenerator:
|
|
|
900
941
|
if system_prompt_override:
|
|
901
942
|
text = f"{system_prompt_override}\n\n{text}"
|
|
902
943
|
|
|
944
|
+
generation_config: dict[str, Any] = {}
|
|
945
|
+
if temperature is not None:
|
|
946
|
+
generation_config["temperature"] = temperature
|
|
903
947
|
request = {
|
|
904
948
|
"contents": [{"parts": [{"text": text}]}],
|
|
905
|
-
"generationConfig":
|
|
906
|
-
"temperature": temperature,
|
|
907
|
-
},
|
|
949
|
+
"generationConfig": generation_config,
|
|
908
950
|
}
|
|
909
951
|
|
|
910
952
|
start = time.perf_counter()
|
|
@@ -942,7 +984,9 @@ class CommitMessageGenerator:
|
|
|
942
984
|
}
|
|
943
985
|
|
|
944
986
|
model = self.config["groq"]["model"]
|
|
945
|
-
temperature =
|
|
987
|
+
temperature = _resolve_temperature(
|
|
988
|
+
model, self.config["groq"].get("temperature")
|
|
989
|
+
)
|
|
946
990
|
|
|
947
991
|
log.debug("Using groq model '%s'.", model)
|
|
948
992
|
|
|
@@ -966,8 +1010,9 @@ class CommitMessageGenerator:
|
|
|
966
1010
|
request = {
|
|
967
1011
|
"model": model,
|
|
968
1012
|
"messages": messages,
|
|
969
|
-
"temperature": temperature,
|
|
970
1013
|
}
|
|
1014
|
+
if temperature is not None:
|
|
1015
|
+
request["temperature"] = temperature
|
|
971
1016
|
|
|
972
1017
|
start = time.perf_counter()
|
|
973
1018
|
response = _http_post( # nosec B113
|
|
@@ -1002,7 +1047,9 @@ class CommitMessageGenerator:
|
|
|
1002
1047
|
}
|
|
1003
1048
|
|
|
1004
1049
|
model = self.config["mistral"]["model"]
|
|
1005
|
-
temperature =
|
|
1050
|
+
temperature = _resolve_temperature(
|
|
1051
|
+
model, self.config["mistral"].get("temperature")
|
|
1052
|
+
)
|
|
1006
1053
|
|
|
1007
1054
|
log.debug("Using mistral model '%s'.", model)
|
|
1008
1055
|
|
|
@@ -1026,8 +1073,9 @@ class CommitMessageGenerator:
|
|
|
1026
1073
|
request = {
|
|
1027
1074
|
"model": model,
|
|
1028
1075
|
"messages": messages,
|
|
1029
|
-
"temperature": temperature,
|
|
1030
1076
|
}
|
|
1077
|
+
if temperature is not None:
|
|
1078
|
+
request["temperature"] = temperature
|
|
1031
1079
|
|
|
1032
1080
|
start = time.perf_counter()
|
|
1033
1081
|
response = _http_post( # nosec B113
|
|
@@ -1187,7 +1235,9 @@ class CommitMessageGenerator:
|
|
|
1187
1235
|
self._start_ollama_server_if_needed()
|
|
1188
1236
|
|
|
1189
1237
|
model = self.config["ollama"]["model"]
|
|
1190
|
-
temperature =
|
|
1238
|
+
temperature = _resolve_temperature(
|
|
1239
|
+
model, self.config["ollama"].get("temperature")
|
|
1240
|
+
)
|
|
1191
1241
|
|
|
1192
1242
|
log.debug("Using ollama model '%s'.", model)
|
|
1193
1243
|
|
|
@@ -1198,13 +1248,14 @@ class CommitMessageGenerator:
|
|
|
1198
1248
|
messages.append({"role": "system", "content": system_prompt_override})
|
|
1199
1249
|
messages.append({"role": "user", "content": content})
|
|
1200
1250
|
|
|
1251
|
+
options: dict[str, Any] = {}
|
|
1252
|
+
if temperature is not None:
|
|
1253
|
+
options["temperature"] = temperature
|
|
1201
1254
|
request: dict[str, Any] = {
|
|
1202
1255
|
"model": model,
|
|
1203
1256
|
"messages": messages,
|
|
1204
1257
|
"stream": False,
|
|
1205
|
-
"options":
|
|
1206
|
-
"temperature": temperature,
|
|
1207
|
-
},
|
|
1258
|
+
"options": options,
|
|
1208
1259
|
}
|
|
1209
1260
|
|
|
1210
1261
|
start = time.perf_counter()
|
|
@@ -1278,11 +1329,12 @@ class CommitMessageGenerator:
|
|
|
1278
1329
|
if model_override is not None
|
|
1279
1330
|
else self.config["openai"]["model"]
|
|
1280
1331
|
)
|
|
1281
|
-
|
|
1332
|
+
raw_temperature = (
|
|
1282
1333
|
temperature_override
|
|
1283
1334
|
if temperature_override is not None
|
|
1284
|
-
else self.config
|
|
1335
|
+
else self.config.get(provider_name, {}).get("temperature")
|
|
1285
1336
|
)
|
|
1337
|
+
temperature = _resolve_temperature(model, raw_temperature)
|
|
1286
1338
|
|
|
1287
1339
|
log.debug("Using %s model '%s'.", provider_name, model)
|
|
1288
1340
|
|
|
@@ -1293,13 +1345,16 @@ class CommitMessageGenerator:
|
|
|
1293
1345
|
|
|
1294
1346
|
messages.append({"role": "user", "content": content})
|
|
1295
1347
|
|
|
1348
|
+
create_kwargs: dict[str, Any] = {
|
|
1349
|
+
"model": model,
|
|
1350
|
+
"messages": messages,
|
|
1351
|
+
"stream": False,
|
|
1352
|
+
}
|
|
1353
|
+
if temperature is not None:
|
|
1354
|
+
create_kwargs["temperature"] = temperature
|
|
1355
|
+
|
|
1296
1356
|
start = time.perf_counter()
|
|
1297
|
-
completion = client.chat.completions.create(
|
|
1298
|
-
model=model,
|
|
1299
|
-
messages=messages,
|
|
1300
|
-
temperature=temperature,
|
|
1301
|
-
stream=False,
|
|
1302
|
-
)
|
|
1357
|
+
completion = client.chat.completions.create(**create_kwargs)
|
|
1303
1358
|
self._last_latency_ms = int((time.perf_counter() - start) * 1000)
|
|
1304
1359
|
|
|
1305
1360
|
usage = completion.usage
|
|
@@ -1333,7 +1388,7 @@ class CommitMessageGenerator:
|
|
|
1333
1388
|
}
|
|
1334
1389
|
|
|
1335
1390
|
model = self.config["xai"]["model"]
|
|
1336
|
-
temperature = self.config["xai"]
|
|
1391
|
+
temperature = _resolve_temperature(model, self.config["xai"].get("temperature"))
|
|
1337
1392
|
|
|
1338
1393
|
log.debug("Using xai model '%s'.", model)
|
|
1339
1394
|
|
|
@@ -1357,8 +1412,9 @@ class CommitMessageGenerator:
|
|
|
1357
1412
|
request = {
|
|
1358
1413
|
"model": model,
|
|
1359
1414
|
"messages": messages,
|
|
1360
|
-
"temperature": temperature,
|
|
1361
1415
|
}
|
|
1416
|
+
if temperature is not None:
|
|
1417
|
+
request["temperature"] = temperature
|
|
1362
1418
|
start = time.perf_counter()
|
|
1363
1419
|
response = _http_post( # nosec B113
|
|
1364
1420
|
url, json=request, headers=headers, timeout=self._timeout("xai")
|
|
@@ -45,7 +45,7 @@ def _validate_config_keys(config: dict[str, Any], reference: dict[str, Any]) ->
|
|
|
45
45
|
Rules:
|
|
46
46
|
- Only global keys (language, default, style, emoji) are validated explicitly
|
|
47
47
|
- At least one provider block must be present
|
|
48
|
-
- Each provider block must define 'model'
|
|
48
|
+
- Each provider block must define 'model' ('temperature' is optional)
|
|
49
49
|
- Unknown top-level keys are rejected
|
|
50
50
|
"""
|
|
51
51
|
log.debug("Validating configuration keys")
|
|
@@ -110,7 +110,7 @@ def _validate_config_keys(config: dict[str, Any], reference: dict[str, Any]) ->
|
|
|
110
110
|
log.error("Provider '%s' configuration must be a mapping", provider)
|
|
111
111
|
raise KeyError("Provider '" + provider + "' must be a mapping")
|
|
112
112
|
|
|
113
|
-
missing_provider_keys = {"model"
|
|
113
|
+
missing_provider_keys = {"model"} - provider_block.keys()
|
|
114
114
|
if missing_provider_keys:
|
|
115
115
|
log.error(
|
|
116
116
|
"Provider '%s' missing required keys: %s",
|